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