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