1*cda5da8dSAndroid Build Coastguard Worker"""Thread-local objects. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Worker(Note that this module provides a Python version of the threading.local 4*cda5da8dSAndroid Build Coastguard Worker class. Depending on the version of Python you're using, there may be a 5*cda5da8dSAndroid Build Coastguard Worker faster one available. You should always import the `local` class from 6*cda5da8dSAndroid Build Coastguard Worker `threading`.) 7*cda5da8dSAndroid Build Coastguard Worker 8*cda5da8dSAndroid Build Coastguard WorkerThread-local objects support the management of thread-local data. 9*cda5da8dSAndroid Build Coastguard WorkerIf you have data that you want to be local to a thread, simply create 10*cda5da8dSAndroid Build Coastguard Workera thread-local object and use its attributes: 11*cda5da8dSAndroid Build Coastguard Worker 12*cda5da8dSAndroid Build Coastguard Worker >>> mydata = local() 13*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number = 42 14*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number 15*cda5da8dSAndroid Build Coastguard Worker 42 16*cda5da8dSAndroid Build Coastguard Worker 17*cda5da8dSAndroid Build Coastguard WorkerYou can also access the local-object's dictionary: 18*cda5da8dSAndroid Build Coastguard Worker 19*cda5da8dSAndroid Build Coastguard Worker >>> mydata.__dict__ 20*cda5da8dSAndroid Build Coastguard Worker {'number': 42} 21*cda5da8dSAndroid Build Coastguard Worker >>> mydata.__dict__.setdefault('widgets', []) 22*cda5da8dSAndroid Build Coastguard Worker [] 23*cda5da8dSAndroid Build Coastguard Worker >>> mydata.widgets 24*cda5da8dSAndroid Build Coastguard Worker [] 25*cda5da8dSAndroid Build Coastguard Worker 26*cda5da8dSAndroid Build Coastguard WorkerWhat's important about thread-local objects is that their data are 27*cda5da8dSAndroid Build Coastguard Workerlocal to a thread. If we access the data in a different thread: 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Worker >>> log = [] 30*cda5da8dSAndroid Build Coastguard Worker >>> def f(): 31*cda5da8dSAndroid Build Coastguard Worker ... items = sorted(mydata.__dict__.items()) 32*cda5da8dSAndroid Build Coastguard Worker ... log.append(items) 33*cda5da8dSAndroid Build Coastguard Worker ... mydata.number = 11 34*cda5da8dSAndroid Build Coastguard Worker ... log.append(mydata.number) 35*cda5da8dSAndroid Build Coastguard Worker 36*cda5da8dSAndroid Build Coastguard Worker >>> import threading 37*cda5da8dSAndroid Build Coastguard Worker >>> thread = threading.Thread(target=f) 38*cda5da8dSAndroid Build Coastguard Worker >>> thread.start() 39*cda5da8dSAndroid Build Coastguard Worker >>> thread.join() 40*cda5da8dSAndroid Build Coastguard Worker >>> log 41*cda5da8dSAndroid Build Coastguard Worker [[], 11] 42*cda5da8dSAndroid Build Coastguard Worker 43*cda5da8dSAndroid Build Coastguard Workerwe get different data. Furthermore, changes made in the other thread 44*cda5da8dSAndroid Build Coastguard Workerdon't affect data seen in this thread: 45*cda5da8dSAndroid Build Coastguard Worker 46*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number 47*cda5da8dSAndroid Build Coastguard Worker 42 48*cda5da8dSAndroid Build Coastguard Worker 49*cda5da8dSAndroid Build Coastguard WorkerOf course, values you get from a local object, including a __dict__ 50*cda5da8dSAndroid Build Coastguard Workerattribute, are for whatever thread was current at the time the 51*cda5da8dSAndroid Build Coastguard Workerattribute was read. For that reason, you generally don't want to save 52*cda5da8dSAndroid Build Coastguard Workerthese values across threads, as they apply only to the thread they 53*cda5da8dSAndroid Build Coastguard Workercame from. 54*cda5da8dSAndroid Build Coastguard Worker 55*cda5da8dSAndroid Build Coastguard WorkerYou can create custom local objects by subclassing the local class: 56*cda5da8dSAndroid Build Coastguard Worker 57*cda5da8dSAndroid Build Coastguard Worker >>> class MyLocal(local): 58*cda5da8dSAndroid Build Coastguard Worker ... number = 2 59*cda5da8dSAndroid Build Coastguard Worker ... def __init__(self, /, **kw): 60*cda5da8dSAndroid Build Coastguard Worker ... self.__dict__.update(kw) 61*cda5da8dSAndroid Build Coastguard Worker ... def squared(self): 62*cda5da8dSAndroid Build Coastguard Worker ... return self.number ** 2 63*cda5da8dSAndroid Build Coastguard Worker 64*cda5da8dSAndroid Build Coastguard WorkerThis can be useful to support default values, methods and 65*cda5da8dSAndroid Build Coastguard Workerinitialization. Note that if you define an __init__ method, it will be 66*cda5da8dSAndroid Build Coastguard Workercalled each time the local object is used in a separate thread. This 67*cda5da8dSAndroid Build Coastguard Workeris necessary to initialize each thread's dictionary. 68*cda5da8dSAndroid Build Coastguard Worker 69*cda5da8dSAndroid Build Coastguard WorkerNow if we create a local object: 70*cda5da8dSAndroid Build Coastguard Worker 71*cda5da8dSAndroid Build Coastguard Worker >>> mydata = MyLocal(color='red') 72*cda5da8dSAndroid Build Coastguard Worker 73*cda5da8dSAndroid Build Coastguard WorkerNow we have a default number: 74*cda5da8dSAndroid Build Coastguard Worker 75*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number 76*cda5da8dSAndroid Build Coastguard Worker 2 77*cda5da8dSAndroid Build Coastguard Worker 78*cda5da8dSAndroid Build Coastguard Workeran initial color: 79*cda5da8dSAndroid Build Coastguard Worker 80*cda5da8dSAndroid Build Coastguard Worker >>> mydata.color 81*cda5da8dSAndroid Build Coastguard Worker 'red' 82*cda5da8dSAndroid Build Coastguard Worker >>> del mydata.color 83*cda5da8dSAndroid Build Coastguard Worker 84*cda5da8dSAndroid Build Coastguard WorkerAnd a method that operates on the data: 85*cda5da8dSAndroid Build Coastguard Worker 86*cda5da8dSAndroid Build Coastguard Worker >>> mydata.squared() 87*cda5da8dSAndroid Build Coastguard Worker 4 88*cda5da8dSAndroid Build Coastguard Worker 89*cda5da8dSAndroid Build Coastguard WorkerAs before, we can access the data in a separate thread: 90*cda5da8dSAndroid Build Coastguard Worker 91*cda5da8dSAndroid Build Coastguard Worker >>> log = [] 92*cda5da8dSAndroid Build Coastguard Worker >>> thread = threading.Thread(target=f) 93*cda5da8dSAndroid Build Coastguard Worker >>> thread.start() 94*cda5da8dSAndroid Build Coastguard Worker >>> thread.join() 95*cda5da8dSAndroid Build Coastguard Worker >>> log 96*cda5da8dSAndroid Build Coastguard Worker [[('color', 'red')], 11] 97*cda5da8dSAndroid Build Coastguard Worker 98*cda5da8dSAndroid Build Coastguard Workerwithout affecting this thread's data: 99*cda5da8dSAndroid Build Coastguard Worker 100*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number 101*cda5da8dSAndroid Build Coastguard Worker 2 102*cda5da8dSAndroid Build Coastguard Worker >>> mydata.color 103*cda5da8dSAndroid Build Coastguard Worker Traceback (most recent call last): 104*cda5da8dSAndroid Build Coastguard Worker ... 105*cda5da8dSAndroid Build Coastguard Worker AttributeError: 'MyLocal' object has no attribute 'color' 106*cda5da8dSAndroid Build Coastguard Worker 107*cda5da8dSAndroid Build Coastguard WorkerNote that subclasses can define slots, but they are not thread 108*cda5da8dSAndroid Build Coastguard Workerlocal. They are shared across threads: 109*cda5da8dSAndroid Build Coastguard Worker 110*cda5da8dSAndroid Build Coastguard Worker >>> class MyLocal(local): 111*cda5da8dSAndroid Build Coastguard Worker ... __slots__ = 'number' 112*cda5da8dSAndroid Build Coastguard Worker 113*cda5da8dSAndroid Build Coastguard Worker >>> mydata = MyLocal() 114*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number = 42 115*cda5da8dSAndroid Build Coastguard Worker >>> mydata.color = 'red' 116*cda5da8dSAndroid Build Coastguard Worker 117*cda5da8dSAndroid Build Coastguard WorkerSo, the separate thread: 118*cda5da8dSAndroid Build Coastguard Worker 119*cda5da8dSAndroid Build Coastguard Worker >>> thread = threading.Thread(target=f) 120*cda5da8dSAndroid Build Coastguard Worker >>> thread.start() 121*cda5da8dSAndroid Build Coastguard Worker >>> thread.join() 122*cda5da8dSAndroid Build Coastguard Worker 123*cda5da8dSAndroid Build Coastguard Workeraffects what we see: 124*cda5da8dSAndroid Build Coastguard Worker 125*cda5da8dSAndroid Build Coastguard Worker >>> mydata.number 126*cda5da8dSAndroid Build Coastguard Worker 11 127*cda5da8dSAndroid Build Coastguard Worker 128*cda5da8dSAndroid Build Coastguard Worker>>> del mydata 129*cda5da8dSAndroid Build Coastguard Worker""" 130*cda5da8dSAndroid Build Coastguard Worker 131*cda5da8dSAndroid Build Coastguard Workerfrom weakref import ref 132*cda5da8dSAndroid Build Coastguard Workerfrom contextlib import contextmanager 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker__all__ = ["local"] 135*cda5da8dSAndroid Build Coastguard Worker 136*cda5da8dSAndroid Build Coastguard Worker# We need to use objects from the threading module, but the threading 137*cda5da8dSAndroid Build Coastguard Worker# module may also want to use our `local` class, if support for locals 138*cda5da8dSAndroid Build Coastguard Worker# isn't compiled in to the `thread` module. This creates potential problems 139*cda5da8dSAndroid Build Coastguard Worker# with circular imports. For that reason, we don't import `threading` 140*cda5da8dSAndroid Build Coastguard Worker# until the bottom of this file (a hack sufficient to worm around the 141*cda5da8dSAndroid Build Coastguard Worker# potential problems). Note that all platforms on CPython do have support 142*cda5da8dSAndroid Build Coastguard Worker# for locals in the `thread` module, and there is no circular import problem 143*cda5da8dSAndroid Build Coastguard Worker# then, so problems introduced by fiddling the order of imports here won't 144*cda5da8dSAndroid Build Coastguard Worker# manifest. 145*cda5da8dSAndroid Build Coastguard Worker 146*cda5da8dSAndroid Build Coastguard Workerclass _localimpl: 147*cda5da8dSAndroid Build Coastguard Worker """A class managing thread-local dicts""" 148*cda5da8dSAndroid Build Coastguard Worker __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__' 149*cda5da8dSAndroid Build Coastguard Worker 150*cda5da8dSAndroid Build Coastguard Worker def __init__(self): 151*cda5da8dSAndroid Build Coastguard Worker # The key used in the Thread objects' attribute dicts. 152*cda5da8dSAndroid Build Coastguard Worker # We keep it a string for speed but make it unlikely to clash with 153*cda5da8dSAndroid Build Coastguard Worker # a "real" attribute. 154*cda5da8dSAndroid Build Coastguard Worker self.key = '_threading_local._localimpl.' + str(id(self)) 155*cda5da8dSAndroid Build Coastguard Worker # { id(Thread) -> (ref(Thread), thread-local dict) } 156*cda5da8dSAndroid Build Coastguard Worker self.dicts = {} 157*cda5da8dSAndroid Build Coastguard Worker 158*cda5da8dSAndroid Build Coastguard Worker def get_dict(self): 159*cda5da8dSAndroid Build Coastguard Worker """Return the dict for the current thread. Raises KeyError if none 160*cda5da8dSAndroid Build Coastguard Worker defined.""" 161*cda5da8dSAndroid Build Coastguard Worker thread = current_thread() 162*cda5da8dSAndroid Build Coastguard Worker return self.dicts[id(thread)][1] 163*cda5da8dSAndroid Build Coastguard Worker 164*cda5da8dSAndroid Build Coastguard Worker def create_dict(self): 165*cda5da8dSAndroid Build Coastguard Worker """Create a new dict for the current thread, and return it.""" 166*cda5da8dSAndroid Build Coastguard Worker localdict = {} 167*cda5da8dSAndroid Build Coastguard Worker key = self.key 168*cda5da8dSAndroid Build Coastguard Worker thread = current_thread() 169*cda5da8dSAndroid Build Coastguard Worker idt = id(thread) 170*cda5da8dSAndroid Build Coastguard Worker def local_deleted(_, key=key): 171*cda5da8dSAndroid Build Coastguard Worker # When the localimpl is deleted, remove the thread attribute. 172*cda5da8dSAndroid Build Coastguard Worker thread = wrthread() 173*cda5da8dSAndroid Build Coastguard Worker if thread is not None: 174*cda5da8dSAndroid Build Coastguard Worker del thread.__dict__[key] 175*cda5da8dSAndroid Build Coastguard Worker def thread_deleted(_, idt=idt): 176*cda5da8dSAndroid Build Coastguard Worker # When the thread is deleted, remove the local dict. 177*cda5da8dSAndroid Build Coastguard Worker # Note that this is suboptimal if the thread object gets 178*cda5da8dSAndroid Build Coastguard Worker # caught in a reference loop. We would like to be called 179*cda5da8dSAndroid Build Coastguard Worker # as soon as the OS-level thread ends instead. 180*cda5da8dSAndroid Build Coastguard Worker local = wrlocal() 181*cda5da8dSAndroid Build Coastguard Worker if local is not None: 182*cda5da8dSAndroid Build Coastguard Worker dct = local.dicts.pop(idt) 183*cda5da8dSAndroid Build Coastguard Worker wrlocal = ref(self, local_deleted) 184*cda5da8dSAndroid Build Coastguard Worker wrthread = ref(thread, thread_deleted) 185*cda5da8dSAndroid Build Coastguard Worker thread.__dict__[key] = wrlocal 186*cda5da8dSAndroid Build Coastguard Worker self.dicts[idt] = wrthread, localdict 187*cda5da8dSAndroid Build Coastguard Worker return localdict 188*cda5da8dSAndroid Build Coastguard Worker 189*cda5da8dSAndroid Build Coastguard Worker 190*cda5da8dSAndroid Build Coastguard Worker@contextmanager 191*cda5da8dSAndroid Build Coastguard Workerdef _patch(self): 192*cda5da8dSAndroid Build Coastguard Worker impl = object.__getattribute__(self, '_local__impl') 193*cda5da8dSAndroid Build Coastguard Worker try: 194*cda5da8dSAndroid Build Coastguard Worker dct = impl.get_dict() 195*cda5da8dSAndroid Build Coastguard Worker except KeyError: 196*cda5da8dSAndroid Build Coastguard Worker dct = impl.create_dict() 197*cda5da8dSAndroid Build Coastguard Worker args, kw = impl.localargs 198*cda5da8dSAndroid Build Coastguard Worker self.__init__(*args, **kw) 199*cda5da8dSAndroid Build Coastguard Worker with impl.locallock: 200*cda5da8dSAndroid Build Coastguard Worker object.__setattr__(self, '__dict__', dct) 201*cda5da8dSAndroid Build Coastguard Worker yield 202*cda5da8dSAndroid Build Coastguard Worker 203*cda5da8dSAndroid Build Coastguard Worker 204*cda5da8dSAndroid Build Coastguard Workerclass local: 205*cda5da8dSAndroid Build Coastguard Worker __slots__ = '_local__impl', '__dict__' 206*cda5da8dSAndroid Build Coastguard Worker 207*cda5da8dSAndroid Build Coastguard Worker def __new__(cls, /, *args, **kw): 208*cda5da8dSAndroid Build Coastguard Worker if (args or kw) and (cls.__init__ is object.__init__): 209*cda5da8dSAndroid Build Coastguard Worker raise TypeError("Initialization arguments are not supported") 210*cda5da8dSAndroid Build Coastguard Worker self = object.__new__(cls) 211*cda5da8dSAndroid Build Coastguard Worker impl = _localimpl() 212*cda5da8dSAndroid Build Coastguard Worker impl.localargs = (args, kw) 213*cda5da8dSAndroid Build Coastguard Worker impl.locallock = RLock() 214*cda5da8dSAndroid Build Coastguard Worker object.__setattr__(self, '_local__impl', impl) 215*cda5da8dSAndroid Build Coastguard Worker # We need to create the thread dict in anticipation of 216*cda5da8dSAndroid Build Coastguard Worker # __init__ being called, to make sure we don't call it 217*cda5da8dSAndroid Build Coastguard Worker # again ourselves. 218*cda5da8dSAndroid Build Coastguard Worker impl.create_dict() 219*cda5da8dSAndroid Build Coastguard Worker return self 220*cda5da8dSAndroid Build Coastguard Worker 221*cda5da8dSAndroid Build Coastguard Worker def __getattribute__(self, name): 222*cda5da8dSAndroid Build Coastguard Worker with _patch(self): 223*cda5da8dSAndroid Build Coastguard Worker return object.__getattribute__(self, name) 224*cda5da8dSAndroid Build Coastguard Worker 225*cda5da8dSAndroid Build Coastguard Worker def __setattr__(self, name, value): 226*cda5da8dSAndroid Build Coastguard Worker if name == '__dict__': 227*cda5da8dSAndroid Build Coastguard Worker raise AttributeError( 228*cda5da8dSAndroid Build Coastguard Worker "%r object attribute '__dict__' is read-only" 229*cda5da8dSAndroid Build Coastguard Worker % self.__class__.__name__) 230*cda5da8dSAndroid Build Coastguard Worker with _patch(self): 231*cda5da8dSAndroid Build Coastguard Worker return object.__setattr__(self, name, value) 232*cda5da8dSAndroid Build Coastguard Worker 233*cda5da8dSAndroid Build Coastguard Worker def __delattr__(self, name): 234*cda5da8dSAndroid Build Coastguard Worker if name == '__dict__': 235*cda5da8dSAndroid Build Coastguard Worker raise AttributeError( 236*cda5da8dSAndroid Build Coastguard Worker "%r object attribute '__dict__' is read-only" 237*cda5da8dSAndroid Build Coastguard Worker % self.__class__.__name__) 238*cda5da8dSAndroid Build Coastguard Worker with _patch(self): 239*cda5da8dSAndroid Build Coastguard Worker return object.__delattr__(self, name) 240*cda5da8dSAndroid Build Coastguard Worker 241*cda5da8dSAndroid Build Coastguard Worker 242*cda5da8dSAndroid Build Coastguard Workerfrom threading import current_thread, RLock 243