blob: b006d76c4e23df7dbf09bc7e668b9eb87e4044af [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
Serhiy Storchaka2085bd02019-06-01 11:00:15 +030059 ... def __init__(self, /, **kw):
Jim Fultond15dc062004-07-14 19:11:50 +000060 ... self.__dict__.update(kw)
61 ... def squared(self):
62 ... return self.number ** 2
63
64This can be useful to support default values, methods and
65initialization. Note that if you define an __init__ method, it will be
66called each time the local object is used in a separate thread. This
67is necessary to initialize each thread's dictionary.
68
69Now if we create a local object:
70
71 >>> mydata = MyLocal(color='red')
72
73Now we have a default number:
74
75 >>> mydata.number
76 2
77
78an initial color:
Tim Peters182b5ac2004-07-18 06:16:08 +000079
Jim Fultond15dc062004-07-14 19:11:50 +000080 >>> mydata.color
81 'red'
82 >>> del mydata.color
83
84And a method that operates on the data:
85
86 >>> mydata.squared()
87 4
88
89As before, we can access the data in a separate thread:
90
91 >>> log = []
92 >>> thread = threading.Thread(target=f)
93 >>> thread.start()
94 >>> thread.join()
95 >>> log
Aaron Gallagher5fb632e2018-02-25 07:03:40 -080096 [[('color', 'red')], 11]
Jim Fultond15dc062004-07-14 19:11:50 +000097
Andrew M. Kuchling3fc2fde2004-07-15 12:17:26 +000098without affecting this thread's data:
Jim Fultond15dc062004-07-14 19:11:50 +000099
100 >>> mydata.number
101 2
102 >>> mydata.color
103 Traceback (most recent call last):
104 ...
105 AttributeError: 'MyLocal' object has no attribute 'color'
106
107Note that subclasses can define slots, but they are not thread
108local. They are shared across threads:
109
110 >>> class MyLocal(local):
111 ... __slots__ = 'number'
112
113 >>> mydata = MyLocal()
114 >>> mydata.number = 42
115 >>> mydata.color = 'red'
116
117So, the separate thread:
118
119 >>> thread = threading.Thread(target=f)
120 >>> thread.start()
121 >>> thread.join()
122
123affects what we see:
124
125 >>> mydata.number
126 11
127
128>>> del mydata
129"""
130
Antoine Pitrou783eea72010-09-07 22:06:17 +0000131from weakref import ref
132from contextlib import contextmanager
133
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000134__all__ = ["local"]
135
136# We need to use objects from the threading module, but the threading
137# module may also want to use our `local` class, if support for locals
138# isn't compiled in to the `thread` module. This creates potential problems
139# with circular imports. For that reason, we don't import `threading`
140# until the bottom of this file (a hack sufficient to worm around the
Antoine Pitrou783eea72010-09-07 22:06:17 +0000141# potential problems). Note that all platforms on CPython do have support
142# for locals in the `thread` module, and there is no circular import problem
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000143# then, so problems introduced by fiddling the order of imports here won't
Antoine Pitrou783eea72010-09-07 22:06:17 +0000144# manifest.
Jim Fultond15dc062004-07-14 19:11:50 +0000145
Antoine Pitrou783eea72010-09-07 22:06:17 +0000146class _localimpl:
147 """A class managing thread-local dicts"""
148 __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
149
150 def __init__(self):
151 # The key used in the Thread objects' attribute dicts.
152 # We keep it a string for speed but make it unlikely to clash with
153 # a "real" attribute.
154 self.key = '_threading_local._localimpl.' + str(id(self))
155 # { id(Thread) -> (ref(Thread), thread-local dict) }
156 self.dicts = {}
157
158 def get_dict(self):
159 """Return the dict for the current thread. Raises KeyError if none
160 defined."""
161 thread = current_thread()
162 return self.dicts[id(thread)][1]
163
164 def create_dict(self):
165 """Create a new dict for the current thread, and return it."""
166 localdict = {}
167 key = self.key
168 thread = current_thread()
169 idt = id(thread)
170 def local_deleted(_, key=key):
171 # When the localimpl is deleted, remove the thread attribute.
172 thread = wrthread()
173 if thread is not None:
174 del thread.__dict__[key]
175 def thread_deleted(_, idt=idt):
176 # When the thread is deleted, remove the local dict.
177 # Note that this is suboptimal if the thread object gets
178 # caught in a reference loop. We would like to be called
179 # as soon as the OS-level thread ends instead.
180 local = wrlocal()
181 if local is not None:
182 dct = local.dicts.pop(idt)
183 wrlocal = ref(self, local_deleted)
184 wrthread = ref(thread, thread_deleted)
185 thread.__dict__[key] = wrlocal
186 self.dicts[idt] = wrthread, localdict
187 return localdict
188
189
190@contextmanager
191def _patch(self):
192 impl = object.__getattribute__(self, '_local__impl')
193 try:
194 dct = impl.get_dict()
195 except KeyError:
196 dct = impl.create_dict()
197 args, kw = impl.localargs
198 self.__init__(*args, **kw)
199 with impl.locallock:
200 object.__setattr__(self, '__dict__', dct)
201 yield
202
203
204class local:
205 __slots__ = '_local__impl', '__dict__'
Jim Fultond15dc062004-07-14 19:11:50 +0000206
Serhiy Storchaka2085bd02019-06-01 11:00:15 +0300207 def __new__(cls, /, *args, **kw):
Jack Diederich561d5aa2010-02-22 19:55:46 +0000208 if (args or kw) and (cls.__init__ is object.__init__):
Jim Fultond15dc062004-07-14 19:11:50 +0000209 raise TypeError("Initialization arguments are not supported")
Antoine Pitrou783eea72010-09-07 22:06:17 +0000210 self = object.__new__(cls)
211 impl = _localimpl()
212 impl.localargs = (args, kw)
213 impl.locallock = RLock()
214 object.__setattr__(self, '_local__impl', impl)
Jim Fultond15dc062004-07-14 19:11:50 +0000215 # We need to create the thread dict in anticipation of
Neal Norwitz7025ce62005-11-25 02:02:50 +0000216 # __init__ being called, to make sure we don't call it
Jim Fultond15dc062004-07-14 19:11:50 +0000217 # again ourselves.
Antoine Pitrou783eea72010-09-07 22:06:17 +0000218 impl.create_dict()
Jim Fultond15dc062004-07-14 19:11:50 +0000219 return self
220
Jim Fultond15dc062004-07-14 19:11:50 +0000221 def __getattribute__(self, name):
Antoine Pitrou783eea72010-09-07 22:06:17 +0000222 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000223 return object.__getattribute__(self, name)
Jim Fultond15dc062004-07-14 19:11:50 +0000224
225 def __setattr__(self, name, value):
Antoine Pitrou1a9a9d52010-08-28 18:17:03 +0000226 if name == '__dict__':
227 raise AttributeError(
228 "%r object attribute '__dict__' is read-only"
229 % self.__class__.__name__)
Antoine Pitrou783eea72010-09-07 22:06:17 +0000230 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000231 return object.__setattr__(self, name, value)
Jim Fultond15dc062004-07-14 19:11:50 +0000232
233 def __delattr__(self, name):
Antoine Pitrou1a9a9d52010-08-28 18:17:03 +0000234 if name == '__dict__':
235 raise AttributeError(
236 "%r object attribute '__dict__' is read-only"
237 % self.__class__.__name__)
Antoine Pitrou783eea72010-09-07 22:06:17 +0000238 with _patch(self):
Jim Fultond15dc062004-07-14 19:11:50 +0000239 return object.__delattr__(self, name)
Jim Fultond15dc062004-07-14 19:11:50 +0000240
Jim Fultond15dc062004-07-14 19:11:50 +0000241
Benjamin Peterson672b8032008-06-11 19:14:14 +0000242from threading import current_thread, RLock