• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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
10a thread-local object and use its attributes:
11
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:
84
85  >>> 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
103without 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
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__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
148class _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
169def _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
186class 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
251from threading import current_thread, RLock
252