blob: c90fde06538420e273923aef3df597ddd5aceaa8 [file] [log] [blame]
Jim Fultond15dc062004-07-14 19:11:50 +00001"""Thread-local objects
2
3(Note that this module provides a Python version of thread
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +00004 threading.local class. Depending on the version of Python you're
Jim Fultond15dc062004-07-14 19:11:50 +00005 using, there may be a faster one available. You should always import
6 the local class from threading.)
7
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():
31 ... items = mydata.__dict__.items()
32 ... items.sort()
33 ... log.append(items)
34 ... mydata.number = 11
35 ... log.append(mydata.number)
36
37 >>> import threading
38 >>> thread = threading.Thread(target=f)
39 >>> thread.start()
40 >>> thread.join()
41 >>> log
42 [[], 11]
43
44we get different data. Furthermore, changes made in the other thread
45don't affect data seen in this thread:
46
47 >>> mydata.number
48 42
49
50Of course, values you get from a local object, including a __dict__
51attribute, are for whatever thread was current at the time the
52attribute was read. For that reason, you generally don't want to save
53these values across threads, as they apply only to the thread they
54came from.
55
56You can create custom local objects by subclassing the local class:
57
58 >>> class MyLocal(local):
59 ... number = 2
60 ... initialized = False
61 ... def __init__(self, **kw):
62 ... if self.initialized:
63 ... raise SystemError('__init__ called too many times')
64 ... self.initialized = True
65 ... self.__dict__.update(kw)
66 ... def squared(self):
67 ... return self.number ** 2
68
69This can be useful to support default values, methods and
70initialization. Note that if you define an __init__ method, it will be
71called each time the local object is used in a separate thread. This
72is necessary to initialize each thread's dictionary.
73
74Now if we create a local object:
75
76 >>> mydata = MyLocal(color='red')
77
78Now we have a default number:
79
80 >>> mydata.number
81 2
82
83an initial color:
Tim Peters182b5ac2004-07-18 06:16:08 +000084
Jim Fultond15dc062004-07-14 19:11:50 +000085 >>> mydata.color
86 'red'
87 >>> del mydata.color
88
89And a method that operates on the data:
90
91 >>> mydata.squared()
92 4
93
94As before, we can access the data in a separate thread:
95
96 >>> log = []
97 >>> thread = threading.Thread(target=f)
98 >>> thread.start()
99 >>> thread.join()
100 >>> log
101 [[('color', 'red'), ('initialized', True)], 11]
102
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +0000103without affecting this thread's data:
Jim Fultond15dc062004-07-14 19:11:50 +0000104
105 >>> mydata.number
106 2
107 >>> mydata.color
108 Traceback (most recent call last):
109 ...
110 AttributeError: 'MyLocal' object has no attribute 'color'
111
112Note that subclasses can define slots, but they are not thread
113local. They are shared across threads:
114
115 >>> class MyLocal(local):
116 ... __slots__ = 'number'
117
118 >>> mydata = MyLocal()
119 >>> mydata.number = 42
120 >>> mydata.color = 'red'
121
122So, the separate thread:
123
124 >>> thread = threading.Thread(target=f)
125 >>> thread.start()
126 >>> thread.join()
127
128affects what we see:
129
130 >>> mydata.number
131 11
132
133>>> del mydata
134"""
135
136# Threading import is at end
137
138class _localbase(object):
139 __slots__ = '_local__key', '_local__args', '_local__lock'
140
141 def __new__(cls, *args, **kw):
142 self = object.__new__(cls)
143 key = '_local__key', 'thread.local.' + str(id(self))
144 object.__setattr__(self, '_local__key', key)
145 object.__setattr__(self, '_local__args', (args, kw))
146 object.__setattr__(self, '_local__lock', RLock())
147
148 if args or kw and (cls.__init__ is object.__init__):
149 raise TypeError("Initialization arguments are not supported")
150
151 # We need to create the thread dict in anticipation of
152 # __init__ being called, to make sire we don't cal it
153 # again ourselves.
154 dict = object.__getattribute__(self, '__dict__')
155 currentThread().__dict__[key] = dict
156
157 return self
158
159def _patch(self):
160 key = object.__getattribute__(self, '_local__key')
161 d = currentThread().__dict__.get(key)
162 if d is None:
163 d = {}
164 currentThread().__dict__[key] = d
165 object.__setattr__(self, '__dict__', d)
166
167 # we have a new instance dict, so call out __init__ if we have
168 # one
169 cls = type(self)
170 if cls.__init__ is not object.__init__:
171 args, kw = object.__getattribute__(self, '_local__args')
172 cls.__init__(self, *args, **kw)
173 else:
174 object.__setattr__(self, '__dict__', d)
175
176class local(_localbase):
177
178 def __getattribute__(self, name):
179 lock = object.__getattribute__(self, '_local__lock')
180 lock.acquire()
181 try:
182 _patch(self)
183 return object.__getattribute__(self, name)
184 finally:
185 lock.release()
186
187 def __setattr__(self, name, value):
188 lock = object.__getattribute__(self, '_local__lock')
189 lock.acquire()
190 try:
191 _patch(self)
192 return object.__setattr__(self, name, value)
193 finally:
194 lock.release()
195
196 def __delattr__(self, name):
197 lock = object.__getattribute__(self, '_local__lock')
198 lock.acquire()
199 try:
200 _patch(self)
201 return object.__delattr__(self, name)
202 finally:
203 lock.release()
204
205
206 def __del__():
207 threading_enumerate = enumerate
208 __getattribute__ = object.__getattribute__
209
210 def __del__(self):
211 key = __getattribute__(self, '_local__key')
212
213 try:
214 threads = list(threading_enumerate())
215 except:
216 # if enumerate fails, as it seems to do during
217 # shutdown, we'll skip cleanup under the assumption
218 # that there is nothing to clean up
Tim Peters182b5ac2004-07-18 06:16:08 +0000219 return
Jim Fultond15dc062004-07-14 19:11:50 +0000220
221 for thread in threads:
222 try:
223 __dict__ = thread.__dict__
224 except AttributeError:
225 # Thread is dying, rest in peace
226 continue
227
228 if key in __dict__:
229 try:
230 del __dict__[key]
Tim Peters182b5ac2004-07-18 06:16:08 +0000231 except KeyError:
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +0000232 pass # didn't have anything in this thread
Jim Fultond15dc062004-07-14 19:11:50 +0000233
234 return __del__
235 __del__ = __del__()
236
237from threading import currentThread, enumerate, RLock