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