blob: 4ec4828144b7e9b265395deecbecde7907bae797 [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001"""Thread-local objects.
Jim Fultond15dc062004-07-14 19:11:50 +00002
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00003(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`.)
Jim Fultond15dc062004-07-14 19:11:50 +00007
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
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +000010a thread-local object and use its attributes:
Jim Fultond15dc062004-07-14 19:11:50 +000011
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():
Guido van Rossumcc2b0162007-02-11 06:12:03 +000031 ... items = sorted(mydata.__dict__.items())
Jim Fultond15dc062004-07-14 19:11:50 +000032 ... 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
43we get different data. Furthermore, changes made in the other thread
44don't affect data seen in this thread:
45
46 >>> mydata.number
47 42
48
49Of course, values you get from a local object, including a __dict__
50attribute, are for whatever thread was current at the time the
51attribute was read. For that reason, you generally don't want to save
52these values across threads, as they apply only to the thread they
53came from.
54
55You 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
68This can be useful to support default values, methods and
69initialization. Note that if you define an __init__ method, it will be
70called each time the local object is used in a separate thread. This
71is necessary to initialize each thread's dictionary.
72
73Now if we create a local object:
74
75 >>> mydata = MyLocal(color='red')
76
77Now we have a default number:
78
79 >>> mydata.number
80 2
81
82an initial color:
Tim Peters182b5ac2004-07-18 06:16:08 +000083
Jim Fultond15dc062004-07-14 19:11:50 +000084 >>> mydata.color
85 'red'
86 >>> del mydata.color
87
88And a method that operates on the data:
89
90 >>> mydata.squared()
91 4
92
93As 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
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +0000102without affecting this thread's data:
Jim Fultond15dc062004-07-14 19:11:50 +0000103
104 >>> mydata.number
105 2
106 >>> mydata.color
107 Traceback (most recent call last):
108 ...
109 AttributeError: 'MyLocal' object has no attribute 'color'
110
111Note that subclasses can define slots, but they are not thread
112local. 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
121So, the separate thread:
122
123 >>> thread = threading.Thread(target=f)
124 >>> thread.start()
125 >>> thread.join()
126
127affects what we see:
128
129 >>> mydata.number
130 11
131
132>>> del mydata
133"""
134
Antoine Pitrou783eea72010-09-07 22:06:17 +0000135from weakref import ref
136from contextlib import contextmanager
137
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000138__all__ = ["local"]
139
140# We need to use objects from the threading module, but the threading
141# module may also want to use our `local` class, if support for locals
142# isn't compiled in to the `thread` module. This creates potential problems
143# with circular imports. For that reason, we don't import `threading`
144# until the bottom of this file (a hack sufficient to worm around the
Antoine Pitrou783eea72010-09-07 22:06:17 +0000145# potential problems). Note that all platforms on CPython do have support
146# for locals in the `thread` module, and there is no circular import problem
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000147# then, so problems introduced by fiddling the order of imports here won't
Antoine Pitrou783eea72010-09-07 22:06:17 +0000148# manifest.
Jim Fultond15dc062004-07-14 19:11:50 +0000149
Antoine Pitrou783eea72010-09-07 22:06:17 +0000150class _localimpl:
151 """A class managing thread-local dicts"""
152 __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
153
154 def __init__(self):
155 # The key used in the Thread objects' attribute dicts.
156 # We keep it a string for speed but make it unlikely to clash with
157 # a "real" attribute.
158 self.key = '_threading_local._localimpl.' + str(id(self))
159 # { id(Thread) -> (ref(Thread), thread-local dict) }
160 self.dicts = {}
161
162 def get_dict(self):
163 """Return the dict for the current thread. Raises KeyError if none
164 defined."""
165 thread = current_thread()
166 return self.dicts[id(thread)][1]
167
168 def create_dict(self):
169 """Create a new dict for the current thread, and return it."""
170 localdict = {}
171 key = self.key
172 thread = current_thread()
173 idt = id(thread)
174 def local_deleted(_, key=key):
175 # When the localimpl is deleted, remove the thread attribute.
176 thread = wrthread()
177 if thread is not None:
178 del thread.__dict__[key]
179 def thread_deleted(_, idt=idt):
180 # When the thread is deleted, remove the local dict.
181 # Note that this is suboptimal if the thread object gets
182 # caught in a reference loop. We would like to be called
183 # as soon as the OS-level thread ends instead.
184 local = wrlocal()
185 if local is not None:
186 dct = local.dicts.pop(idt)
187 wrlocal = ref(self, local_deleted)
188 wrthread = ref(thread, thread_deleted)
189 thread.__dict__[key] = wrlocal
190 self.dicts[idt] = wrthread, localdict
191 return localdict
192
193
194@contextmanager
195def _patch(self):
196 impl = object.__getattribute__(self, '_local__impl')
197 try:
198 dct = impl.get_dict()
199 except KeyError:
200 dct = impl.create_dict()
201 args, kw = impl.localargs
202 self.__init__(*args, **kw)
203 with impl.locallock:
204 object.__setattr__(self, '__dict__', dct)
205 yield
206
207
208class local:
209 __slots__ = '_local__impl', '__dict__'
Jim Fultond15dc062004-07-14 19:11:50 +0000210
211 def __new__(cls, *args, **kw):
Jack Diederich561d5aa2010-02-22 19:55:46 +0000212 if (args or kw) and (cls.__init__ is object.__init__):
Jim Fultond15dc062004-07-14 19:11:50 +0000213 raise TypeError("Initialization arguments are not supported")
Antoine Pitrou783eea72010-09-07 22:06:17 +0000214 self = object.__new__(cls)
215 impl = _localimpl()
216 impl.localargs = (args, kw)
217 impl.locallock = RLock()
218 object.__setattr__(self, '_local__impl', impl)
Jim Fultond15dc062004-07-14 19:11:50 +0000219 # We need to create the thread dict in anticipation of
Neal Norwitz7025ce62005-11-25 02:02:50 +0000220 # __init__ being called, to make sure we don't call it
Jim Fultond15dc062004-07-14 19:11:50 +0000221 # again ourselves.
Antoine Pitrou783eea72010-09-07 22:06:17 +0000222 impl.create_dict()
Jim Fultond15dc062004-07-14 19:11:50 +0000223 return self
224
Jim Fultond15dc062004-07-14 19:11:50 +0000225 def __getattribute__(self, name):
Antoine Pitrou783eea72010-09-07 22:06:17 +0000226 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000227 return object.__getattribute__(self, name)
Jim Fultond15dc062004-07-14 19:11:50 +0000228
229 def __setattr__(self, name, value):
Antoine Pitrou1a9a9d52010-08-28 18:17:03 +0000230 if name == '__dict__':
231 raise AttributeError(
232 "%r object attribute '__dict__' is read-only"
233 % self.__class__.__name__)
Antoine Pitrou783eea72010-09-07 22:06:17 +0000234 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000235 return object.__setattr__(self, name, value)
Jim Fultond15dc062004-07-14 19:11:50 +0000236
237 def __delattr__(self, name):
Antoine Pitrou1a9a9d52010-08-28 18:17:03 +0000238 if name == '__dict__':
239 raise AttributeError(
240 "%r object attribute '__dict__' is read-only"
241 % self.__class__.__name__)
Antoine Pitrou783eea72010-09-07 22:06:17 +0000242 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000243 return object.__delattr__(self, name)
Jim Fultond15dc062004-07-14 19:11:50 +0000244
Jim Fultond15dc062004-07-14 19:11:50 +0000245
Benjamin Peterson672b8032008-06-11 19:14:14 +0000246from threading import current_thread, RLock