threadlocal objects note that this module provides a python version of the threading local class depending on the version of python you re using there may be a faster one available you should always import the local class from threading threadlocal objects support the management of threadlocal data if you have data that you want to be local to a thread simply create a threadlocal object and use its attributes mydata local mydata number 42 mydata number 42 you can also access the localobject s dictionary mydata dict number 42 mydata dict setdefault widgets mydata widgets what s important about threadlocal objects is that their data are local to a thread if we access the data in a different thread log def f items sortedmydata dict items log appenditems mydata number 11 log appendmydata number import threading thread threading threadtargetf thread start thread join log 11 we get different data furthermore changes made in the other thread don t affect data seen in this thread mydata number 42 of course values you get from a local object including a dict attribute are for whatever thread was current at the time the attribute was read for that reason you generally don t want to save these values across threads as they apply only to the thread they came from you can create custom local objects by subclassing the local class class mylocallocal number 2 def initself kw self dict updatekw def squaredself return self number 2 this can be useful to support default values methods and initialization note that if you define an init method it will be called each time the local object is used in a separate thread this is necessary to initialize each thread s dictionary now if we create a local object mydata mylocalcolor red now we have a default number mydata number 2 an initial color mydata color red del mydata color and a method that operates on the data mydata squared 4 as before we can access the data in a separate thread log thread threading threadtargetf thread start thread join log color red 11 without affecting this thread s data mydata number 2 mydata color traceback most recent call last attributeerror mylocal object has no attribute color note that subclasses can define slots but they are not thread local they are shared across threads class mylocallocal slots number mydata mylocal mydata number 42 mydata color red so the separate thread thread threading threadtargetf thread start thread join affects what we see mydata number 11 del mydata we need to use objects from the threading module but the threading module may also want to use our local class if support for locals isn t compiled in to the thread module this creates potential problems with circular imports for that reason we don t import threading until the bottom of this file a hack sufficient to worm around the potential problems note that all platforms on cpython do have support for locals in the thread module and there is no circular import problem then so problems introduced by fiddling the order of imports here won t manifest a class managing threadlocal dicts slots key dicts localargs locallock weakref def initself the key used in the thread objects attribute dicts we keep it a string for speed but make it unlikely to clash with a real attribute self key threadinglocal localimpl stridself idthread refthread threadlocal dict self dicts def getdictself create a new dict for the current thread and return it localdict key self key thread currentthread idt idthread def localdeleted keykey when the localimpl is deleted remove the thread attribute thread wrthread if thread is not none del thread dictkey def threaddeleted idtidt when the thread is deleted remove the local dict note that this is suboptimal if the thread object gets caught in a reference loop we would like to be called as soon as the oslevel thread ends instead local wrlocal if local is not none dct local dicts popidt wrlocal refself localdeleted wrthread refthread threaddeleted thread dictkey wrlocal self dictsidt wrthread localdict return localdict contextmanager def patchself impl object getattributeself localimpl try dct impl getdict except keyerror dct impl createdict args kw impl localargs self initargs kw with impl locallock object setattrself dict dct yield class local slots localimpl dict def newcls args kw if args or kw and cls init is object init raise typeerrorinitialization arguments are not supported self object newcls impl localimpl impl localargs args kw impl locallock rlock object setattrself localimpl impl we need to create the thread dict in anticipation of init being called to make sure we don t call it again ourselves impl createdict return self def getattributeself name with patchself return object getattributeself name def setattrself name value if name dict raise attributeerror r object attribute dict is readonly self class name with patchself return object setattrself name value def delattrself name if name dict raise attributeerror r object attribute dict is readonly self class name with patchself return object delattrself name from threading import currentthread rlock we need to use objects from the threading module but the threading module may also want to use our local class if support for locals isn t compiled in to the thread module this creates potential problems with circular imports for that reason we don t import threading until the bottom of this file a hack sufficient to worm around the potential problems note that all platforms on cpython do have support for locals in the thread module and there is no circular import problem then so problems introduced by fiddling the order of imports here won t manifest a class managing thread local dicts the key used in the thread objects attribute dicts we keep it a string for speed but make it unlikely to clash with a real attribute id thread ref thread thread local dict return the dict for the current thread raises keyerror if none defined create a new dict for the current thread and return it when the localimpl is deleted remove the thread attribute when the thread is deleted remove the local dict note that this is suboptimal if the thread object gets caught in a reference loop we would like to be called as soon as the os level thread ends instead we need to create the thread dict in anticipation of __init__ being called to make sure we don t call it again ourselves
from weakref import ref
from contextlib import contextmanager

__all__ = ["local"]











class _localimpl:
    
    __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'

    def __init__(self):
        
        
        
        self.key = '_threading_local._localimpl.' + str(id(self))
        
        self.dicts = {}

    def get_dict(self):
        
        thread = current_thread()
        return self.dicts[id(thread)][1]

    def create_dict(self):
        
        localdict = {}
        key = self.key
        thread = current_thread()
        idt = id(thread)
        def local_deleted(_, key=key):
            
            thread = wrthread()
            if thread is not None:
                del thread.__dict__[key]
        def thread_deleted(_, idt=idt):
            
            
            
            
            local = wrlocal()
            if local is not None:
                dct = local.dicts.pop(idt)
        wrlocal = ref(self, local_deleted)
        wrthread = ref(thread, thread_deleted)
        thread.__dict__[key] = wrlocal
        self.dicts[idt] = wrthread, localdict
        return localdict


@contextmanager
def _patch(self):
    impl = object.__getattribute__(self, '_local__impl')
    try:
        dct = impl.get_dict()
    except KeyError:
        dct = impl.create_dict()
        args, kw = impl.localargs
        self.__init__(*args, **kw)
    with impl.locallock:
        object.__setattr__(self, '__dict__', dct)
        yield


class local:
    __slots__ = '_local__impl', '__dict__'

    def __new__(cls, /, *args, **kw):
        if (args or kw) and (cls.__init__ is object.__init__):
            raise TypeError("Initialization arguments are not supported")
        self = object.__new__(cls)
        impl = _localimpl()
        impl.localargs = (args, kw)
        impl.locallock = RLock()
        object.__setattr__(self, '_local__impl', impl)
        
        
        
        impl.create_dict()
        return self

    def __getattribute__(self, name):
        with _patch(self):
            return object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        if name == '__dict__':
            raise AttributeError(
                "%r object attribute '__dict__' is read-only"
                % self.__class__.__name__)
        with _patch(self):
            return object.__setattr__(self, name, value)

    def __delattr__(self, name):
        if name == '__dict__':
            raise AttributeError(
                "%r object attribute '__dict__' is read-only"
                % self.__class__.__name__)
        with _patch(self):
            return object.__delattr__(self, name)


from threading import current_thread, RLock