~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/_threading_local.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

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