• 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  ...     def __init__(self, **kw):
61  ...         self.__dict__.update(kw)
62  ...     def squared(self):
63  ...         return self.number ** 2
64
65This can be useful to support default values, methods and
66initialization.  Note that if you define an __init__ method, it will be
67called each time the local object is used in a separate thread.  This
68is necessary to initialize each thread's dictionary.
69
70Now if we create a local object:
71
72  >>> mydata = MyLocal(color='red')
73
74Now we have a default number:
75
76  >>> mydata.number
77  2
78
79an initial color:
80
81  >>> mydata.color
82  'red'
83  >>> del mydata.color
84
85And a method that operates on the data:
86
87  >>> mydata.squared()
88  4
89
90As 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
99without 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
108Note that subclasses can define slots, but they are not thread
109local. 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
118So, the separate thread:
119
120  >>> thread = threading.Thread(target=f)
121  >>> thread.start()
122  >>> thread.join()
123
124affects 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
144class _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
165def _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
182class 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
247from threading import current_thread, RLock
248