1 | """Thread-local objects.
|
---|
2 |
|
---|
3 | (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`.)
|
---|
7 |
|
---|
8 | Thread-local objects support the management of thread-local data.
|
---|
9 | If you have data that you want to be local to a thread, simply create
|
---|
10 | a thread-local object and use its attributes:
|
---|
11 |
|
---|
12 | >>> mydata = local()
|
---|
13 | >>> mydata.number = 42
|
---|
14 | >>> mydata.number
|
---|
15 | 42
|
---|
16 |
|
---|
17 | You 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 |
|
---|
26 | What's important about thread-local objects is that their data are
|
---|
27 | local 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 |
|
---|
44 | we get different data. Furthermore, changes made in the other thread
|
---|
45 | don't affect data seen in this thread:
|
---|
46 |
|
---|
47 | >>> mydata.number
|
---|
48 | 42
|
---|
49 |
|
---|
50 | Of course, values you get from a local object, including a __dict__
|
---|
51 | attribute, are for whatever thread was current at the time the
|
---|
52 | attribute was read. For that reason, you generally don't want to save
|
---|
53 | these values across threads, as they apply only to the thread they
|
---|
54 | came from.
|
---|
55 |
|
---|
56 | You 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 |
|
---|
69 | This can be useful to support default values, methods and
|
---|
70 | initialization. Note that if you define an __init__ method, it will be
|
---|
71 | called each time the local object is used in a separate thread. This
|
---|
72 | is necessary to initialize each thread's dictionary.
|
---|
73 |
|
---|
74 | Now if we create a local object:
|
---|
75 |
|
---|
76 | >>> mydata = MyLocal(color='red')
|
---|
77 |
|
---|
78 | Now we have a default number:
|
---|
79 |
|
---|
80 | >>> mydata.number
|
---|
81 | 2
|
---|
82 |
|
---|
83 | an initial color:
|
---|
84 |
|
---|
85 | >>> mydata.color
|
---|
86 | 'red'
|
---|
87 | >>> del mydata.color
|
---|
88 |
|
---|
89 | And a method that operates on the data:
|
---|
90 |
|
---|
91 | >>> mydata.squared()
|
---|
92 | 4
|
---|
93 |
|
---|
94 | As 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 |
|
---|
103 | without affecting this thread's data:
|
---|
104 |
|
---|
105 | >>> mydata.number
|
---|
106 | 2
|
---|
107 | >>> mydata.color
|
---|
108 | Traceback (most recent call last):
|
---|
109 | ...
|
---|
110 | AttributeError: 'MyLocal' object has no attribute 'color'
|
---|
111 |
|
---|
112 | Note that subclasses can define slots, but they are not thread
|
---|
113 | local. 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 |
|
---|
122 | So, the separate thread:
|
---|
123 |
|
---|
124 | >>> thread = threading.Thread(target=f)
|
---|
125 | >>> thread.start()
|
---|
126 | >>> thread.join()
|
---|
127 |
|
---|
128 | affects what we see:
|
---|
129 |
|
---|
130 | >>> mydata.number
|
---|
131 | 11
|
---|
132 |
|
---|
133 | >>> del mydata
|
---|
134 | """
|
---|
135 |
|
---|
136 | __all__ = ["local"]
|
---|
137 |
|
---|
138 | # We need to use objects from the threading module, but the threading
|
---|
139 | # module may also want to use our `local` class, if support for locals
|
---|
140 | # isn't compiled in to the `thread` module. This creates potential problems
|
---|
141 | # with circular imports. For that reason, we don't import `threading`
|
---|
142 | # until the bottom of this file (a hack sufficient to worm around the
|
---|
143 | # potential problems). Note that almost all platforms do have support for
|
---|
144 | # locals in the `thread` module, and there is no circular import problem
|
---|
145 | # then, so problems introduced by fiddling the order of imports here won't
|
---|
146 | # manifest on most boxes.
|
---|
147 |
|
---|
148 | class _localbase(object):
|
---|
149 | __slots__ = '_local__key', '_local__args', '_local__lock'
|
---|
150 |
|
---|
151 | def __new__(cls, *args, **kw):
|
---|
152 | self = object.__new__(cls)
|
---|
153 | key = '_local__key', 'thread.local.' + str(id(self))
|
---|
154 | object.__setattr__(self, '_local__key', key)
|
---|
155 | object.__setattr__(self, '_local__args', (args, kw))
|
---|
156 | object.__setattr__(self, '_local__lock', RLock())
|
---|
157 |
|
---|
158 | if (args or kw) and (cls.__init__ is object.__init__):
|
---|
159 | raise TypeError("Initialization arguments are not supported")
|
---|
160 |
|
---|
161 | # We need to create the thread dict in anticipation of
|
---|
162 | # __init__ being called, to make sure we don't call it
|
---|
163 | # again ourselves.
|
---|
164 | dict = object.__getattribute__(self, '__dict__')
|
---|
165 | current_thread().__dict__[key] = dict
|
---|
166 |
|
---|
167 | return self
|
---|
168 |
|
---|
169 | def _patch(self):
|
---|
170 | key = object.__getattribute__(self, '_local__key')
|
---|
171 | d = current_thread().__dict__.get(key)
|
---|
172 | if d is None:
|
---|
173 | d = {}
|
---|
174 | current_thread().__dict__[key] = d
|
---|
175 | object.__setattr__(self, '__dict__', d)
|
---|
176 |
|
---|
177 | # we have a new instance dict, so call out __init__ if we have
|
---|
178 | # one
|
---|
179 | cls = type(self)
|
---|
180 | if cls.__init__ is not object.__init__:
|
---|
181 | args, kw = object.__getattribute__(self, '_local__args')
|
---|
182 | cls.__init__(self, *args, **kw)
|
---|
183 | else:
|
---|
184 | object.__setattr__(self, '__dict__', d)
|
---|
185 |
|
---|
186 | class local(_localbase):
|
---|
187 |
|
---|
188 | def __getattribute__(self, name):
|
---|
189 | lock = object.__getattribute__(self, '_local__lock')
|
---|
190 | lock.acquire()
|
---|
191 | try:
|
---|
192 | _patch(self)
|
---|
193 | return object.__getattribute__(self, name)
|
---|
194 | finally:
|
---|
195 | lock.release()
|
---|
196 |
|
---|
197 | def __setattr__(self, name, value):
|
---|
198 | if name == '__dict__':
|
---|
199 | raise AttributeError(
|
---|
200 | "%r object attribute '__dict__' is read-only"
|
---|
201 | % self.__class__.__name__)
|
---|
202 | lock = object.__getattribute__(self, '_local__lock')
|
---|
203 | lock.acquire()
|
---|
204 | try:
|
---|
205 | _patch(self)
|
---|
206 | return object.__setattr__(self, name, value)
|
---|
207 | finally:
|
---|
208 | lock.release()
|
---|
209 |
|
---|
210 | def __delattr__(self, name):
|
---|
211 | if name == '__dict__':
|
---|
212 | raise AttributeError(
|
---|
213 | "%r object attribute '__dict__' is read-only"
|
---|
214 | % self.__class__.__name__)
|
---|
215 | lock = object.__getattribute__(self, '_local__lock')
|
---|
216 | lock.acquire()
|
---|
217 | try:
|
---|
218 | _patch(self)
|
---|
219 | return object.__delattr__(self, name)
|
---|
220 | finally:
|
---|
221 | lock.release()
|
---|
222 |
|
---|
223 | def __del__(self):
|
---|
224 | import threading
|
---|
225 |
|
---|
226 | key = object.__getattribute__(self, '_local__key')
|
---|
227 |
|
---|
228 | try:
|
---|
229 | # We use the non-locking API since we might already hold the lock
|
---|
230 | # (__del__ can be called at any point by the cyclic GC).
|
---|
231 | threads = threading._enumerate()
|
---|
232 | except:
|
---|
233 | # If enumerating the current threads fails, as it seems to do
|
---|
234 | # during shutdown, we'll skip cleanup under the assumption
|
---|
235 | # that there is nothing to clean up.
|
---|
236 | return
|
---|
237 |
|
---|
238 | for thread in threads:
|
---|
239 | try:
|
---|
240 | __dict__ = thread.__dict__
|
---|
241 | except AttributeError:
|
---|
242 | # Thread is dying, rest in peace.
|
---|
243 | continue
|
---|
244 |
|
---|
245 | if key in __dict__:
|
---|
246 | try:
|
---|
247 | del __dict__[key]
|
---|
248 | except KeyError:
|
---|
249 | pass # didn't have anything in this thread
|
---|
250 |
|
---|
251 | from threading import current_thread, RLock
|
---|