blob: 40a07f068822a139c581d6dbe8ea1cfe4d33b1c9 [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001"""Thread-local objects.
Jim Fultond15dc062004-07-14 19:11:50 +00002
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00003(Note that this module provides a Python version of the threading.local
4 class. Depending on the version of Python you're using, there may be a
5 faster one available. You should always import the `local` class from
6 `threading`.)
Jim Fultond15dc062004-07-14 19:11:50 +00007
8Thread-local objects support the management of thread-local data.
9If you have data that you want to be local to a thread, simply create
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +000010a thread-local object and use its attributes:
Jim Fultond15dc062004-07-14 19:11:50 +000011
12 >>> mydata = local()
13 >>> mydata.number = 42
14 >>> mydata.number
15 42
16
17You can also access the local-object's dictionary:
18
19 >>> mydata.__dict__
20 {'number': 42}
21 >>> mydata.__dict__.setdefault('widgets', [])
22 []
23 >>> mydata.widgets
24 []
25
26What's important about thread-local objects is that their data are
27local to a thread. If we access the data in a different thread:
28
29 >>> log = []
30 >>> def f():
Guido van Rossumcc2b0162007-02-11 06:12:03 +000031 ... items = sorted(mydata.__dict__.items())
Jim Fultond15dc062004-07-14 19:11:50 +000032 ... log.append(items)
33 ... mydata.number = 11
34 ... log.append(mydata.number)
35
36 >>> import threading
37 >>> thread = threading.Thread(target=f)
38 >>> thread.start()
39 >>> thread.join()
40 >>> log
41 [[], 11]
42
43we get different data. Furthermore, changes made in the other thread
44don't affect data seen in this thread:
45
46 >>> mydata.number
47 42
48
49Of course, values you get from a local object, including a __dict__
50attribute, are for whatever thread was current at the time the
51attribute was read. For that reason, you generally don't want to save
52these values across threads, as they apply only to the thread they
53came from.
54
55You can create custom local objects by subclassing the local class:
56
57 >>> class MyLocal(local):
58 ... number = 2
59 ... initialized = False
60 ... def __init__(self, **kw):
61 ... if self.initialized:
62 ... raise SystemError('__init__ called too many times')
63 ... self.initialized = True
64 ... self.__dict__.update(kw)
65 ... def squared(self):
66 ... return self.number ** 2
67
68This can be useful to support default values, methods and
69initialization. Note that if you define an __init__ method, it will be
70called each time the local object is used in a separate thread. This
71is necessary to initialize each thread's dictionary.
72
73Now if we create a local object:
74
75 >>> mydata = MyLocal(color='red')
76
77Now we have a default number:
78
79 >>> mydata.number
80 2
81
82an initial color:
Tim Peters182b5ac2004-07-18 06:16:08 +000083
Jim Fultond15dc062004-07-14 19:11:50 +000084 >>> mydata.color
85 'red'
86 >>> del mydata.color
87
88And a method that operates on the data:
89
90 >>> mydata.squared()
91 4
92
93As before, we can access the data in a separate thread:
94
95 >>> log = []
96 >>> thread = threading.Thread(target=f)
97 >>> thread.start()
98 >>> thread.join()
99 >>> log
100 [[('color', 'red'), ('initialized', True)], 11]
101
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +0000102without affecting this thread's data:
Jim Fultond15dc062004-07-14 19:11:50 +0000103
104 >>> mydata.number
105 2
106 >>> mydata.color
107 Traceback (most recent call last):
108 ...
109 AttributeError: 'MyLocal' object has no attribute 'color'
110
111Note that subclasses can define slots, but they are not thread
112local. They are shared across threads:
113
114 >>> class MyLocal(local):
115 ... __slots__ = 'number'
116
117 >>> mydata = MyLocal()
118 >>> mydata.number = 42
119 >>> mydata.color = 'red'
120
121So, the separate thread:
122
123 >>> thread = threading.Thread(target=f)
124 >>> thread.start()
125 >>> thread.join()
126
127affects what we see:
128
129 >>> mydata.number
130 11
131
132>>> del mydata
133"""
134
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000135__all__ = ["local"]
136
137# We need to use objects from the threading module, but the threading
138# module may also want to use our `local` class, if support for locals
139# isn't compiled in to the `thread` module. This creates potential problems
140# with circular imports. For that reason, we don't import `threading`
141# until the bottom of this file (a hack sufficient to worm around the
142# potential problems). Note that almost all platforms do have support for
143# locals in the `thread` module, and there is no circular import problem
144# then, so problems introduced by fiddling the order of imports here won't
145# manifest on most boxes.
Jim Fultond15dc062004-07-14 19:11:50 +0000146
147class _localbase(object):
148 __slots__ = '_local__key', '_local__args', '_local__lock'
149
150 def __new__(cls, *args, **kw):
151 self = object.__new__(cls)
152 key = '_local__key', 'thread.local.' + str(id(self))
153 object.__setattr__(self, '_local__key', key)
154 object.__setattr__(self, '_local__args', (args, kw))
155 object.__setattr__(self, '_local__lock', RLock())
156
157 if args or kw and (cls.__init__ is object.__init__):
158 raise TypeError("Initialization arguments are not supported")
159
160 # We need to create the thread dict in anticipation of
Neal Norwitz7025ce62005-11-25 02:02:50 +0000161 # __init__ being called, to make sure we don't call it
Jim Fultond15dc062004-07-14 19:11:50 +0000162 # again ourselves.
163 dict = object.__getattribute__(self, '__dict__')
164 currentThread().__dict__[key] = dict
165
166 return self
167
168def _patch(self):
169 key = object.__getattribute__(self, '_local__key')
170 d = currentThread().__dict__.get(key)
171 if d is None:
172 d = {}
173 currentThread().__dict__[key] = d
174 object.__setattr__(self, '__dict__', d)
175
176 # we have a new instance dict, so call out __init__ if we have
177 # one
178 cls = type(self)
179 if cls.__init__ is not object.__init__:
180 args, kw = object.__getattribute__(self, '_local__args')
181 cls.__init__(self, *args, **kw)
182 else:
183 object.__setattr__(self, '__dict__', d)
184
185class local(_localbase):
186
187 def __getattribute__(self, name):
188 lock = object.__getattribute__(self, '_local__lock')
189 lock.acquire()
190 try:
191 _patch(self)
192 return object.__getattribute__(self, name)
193 finally:
194 lock.release()
195
196 def __setattr__(self, name, value):
197 lock = object.__getattribute__(self, '_local__lock')
198 lock.acquire()
199 try:
200 _patch(self)
201 return object.__setattr__(self, name, value)
202 finally:
203 lock.release()
204
205 def __delattr__(self, name):
206 lock = object.__getattribute__(self, '_local__lock')
207 lock.acquire()
208 try:
209 _patch(self)
210 return object.__delattr__(self, name)
211 finally:
212 lock.release()
213
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000214 def __del__(self):
215 import threading
Jim Fultond15dc062004-07-14 19:11:50 +0000216
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000217 key = object.__getattribute__(self, '_local__key')
Jim Fultond15dc062004-07-14 19:11:50 +0000218
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000219 try:
220 threads = list(threading.enumerate())
221 except:
222 # If enumerate fails, as it seems to do during
223 # shutdown, we'll skip cleanup under the assumption
224 # that there is nothing to clean up.
225 return
Jim Fultond15dc062004-07-14 19:11:50 +0000226
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000227 for thread in threads:
Jim Fultond15dc062004-07-14 19:11:50 +0000228 try:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000229 __dict__ = thread.__dict__
230 except AttributeError:
231 # Thread is dying, rest in peace.
232 continue
Jim Fultond15dc062004-07-14 19:11:50 +0000233
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000234 if key in __dict__:
Jim Fultond15dc062004-07-14 19:11:50 +0000235 try:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000236 del __dict__[key]
237 except KeyError:
238 pass # didn't have anything in this thread
Jim Fultond15dc062004-07-14 19:11:50 +0000239
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000240from threading import currentThread, RLock