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 ... def __init__(self, **kw): 60 ... self.__dict__.update(kw) 61 ... def squared(self): 62 ... return self.number ** 2 63 64 This can be useful to support default values, methods and 65 initialization. Note that if you define an __init__ method, it will be 66 called each time the local object is used in a separate thread. This 67 is necessary to initialize each thread's dictionary. 68 69 Now if we create a local object: 70 71 >>> mydata = MyLocal(color='red') 72 73 Now we have a default number: 74 75 >>> mydata.number 76 2 77 78 an initial color: 79 80 >>> mydata.color 81 'red' 82 >>> del mydata.color 83 84 And a method that operates on the data: 85 86 >>> mydata.squared() 87 4 88 89 As 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 96 [[('color', 'red')], 11] 97 98 without affecting this thread's data: 99 100 >>> mydata.number 101 2 102 >>> mydata.color 103 Traceback (most recent call last): 104 ... 105 AttributeError: 'MyLocal' object has no attribute 'color' 106 107 Note that subclasses can define slots, but they are not thread 108 local. 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 117 So, the separate thread: 118 119 >>> thread = threading.Thread(target=f) 120 >>> thread.start() 121 >>> thread.join() 122 123 affects what we see: 124 125 >>> mydata.number 126 11 127 128 >>> del mydata 129 """ 130 131 from weakref import ref 132 from contextlib import contextmanager 133 134 __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 141 # 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 143 # then, so problems introduced by fiddling the order of imports here won't 144 # manifest. 145 146 class _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 191 def _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 204 class local: 205 __slots__ = '_local__impl', '__dict__' 206 207 def __new__(cls, *args, **kw): 208 if (args or kw) and (cls.__init__ is object.__init__): 209 raise TypeError("Initialization arguments are not supported") 210 self = object.__new__(cls) 211 impl = _localimpl() 212 impl.localargs = (args, kw) 213 impl.locallock = RLock() 214 object.__setattr__(self, '_local__impl', impl) 215 # We need to create the thread dict in anticipation of 216 # __init__ being called, to make sure we don't call it 217 # again ourselves. 218 impl.create_dict() 219 return self 220 221 def __getattribute__(self, name): 222 with _patch(self): 223 return object.__getattribute__(self, name) 224 225 def __setattr__(self, name, value): 226 if name == '__dict__': 227 raise AttributeError( 228 "%r object attribute '__dict__' is read-only" 229 % self.__class__.__name__) 230 with _patch(self): 231 return object.__setattr__(self, name, value) 232 233 def __delattr__(self, name): 234 if name == '__dict__': 235 raise AttributeError( 236 "%r object attribute '__dict__' is read-only" 237 % self.__class__.__name__) 238 with _patch(self): 239 return object.__delattr__(self, name) 240 241 242 from threading import current_thread, RLock 243