mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	is friendlier towards reference cycles. This change is not normally visible since an optimized C implementation (_thread._local) is used instead.
		
			
				
	
	
		
			246 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Thread-local 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`.)
 | 
						|
 | 
						|
Thread-local objects support the management of thread-local data.
 | 
						|
If you have data that you want to be local to a thread, simply create
 | 
						|
a thread-local object and use its attributes:
 | 
						|
 | 
						|
  >>> mydata = local()
 | 
						|
  >>> mydata.number = 42
 | 
						|
  >>> mydata.number
 | 
						|
  42
 | 
						|
 | 
						|
You can also access the local-object's dictionary:
 | 
						|
 | 
						|
  >>> mydata.__dict__
 | 
						|
  {'number': 42}
 | 
						|
  >>> mydata.__dict__.setdefault('widgets', [])
 | 
						|
  []
 | 
						|
  >>> mydata.widgets
 | 
						|
  []
 | 
						|
 | 
						|
What's important about thread-local objects is that their data are
 | 
						|
local to a thread. If we access the data in a different thread:
 | 
						|
 | 
						|
  >>> log = []
 | 
						|
  >>> def f():
 | 
						|
  ...     items = sorted(mydata.__dict__.items())
 | 
						|
  ...     log.append(items)
 | 
						|
  ...     mydata.number = 11
 | 
						|
  ...     log.append(mydata.number)
 | 
						|
 | 
						|
  >>> import threading
 | 
						|
  >>> thread = threading.Thread(target=f)
 | 
						|
  >>> 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 MyLocal(local):
 | 
						|
  ...     number = 2
 | 
						|
  ...     initialized = False
 | 
						|
  ...     def __init__(self, **kw):
 | 
						|
  ...         if self.initialized:
 | 
						|
  ...             raise SystemError('__init__ called too many times')
 | 
						|
  ...         self.initialized = True
 | 
						|
  ...         self.__dict__.update(kw)
 | 
						|
  ...     def squared(self):
 | 
						|
  ...         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 = MyLocal(color='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.Thread(target=f)
 | 
						|
  >>> thread.start()
 | 
						|
  >>> thread.join()
 | 
						|
  >>> log
 | 
						|
  [[('color', 'red'), ('initialized', True)], 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 MyLocal(local):
 | 
						|
  ...     __slots__ = 'number'
 | 
						|
 | 
						|
  >>> mydata = MyLocal()
 | 
						|
  >>> mydata.number = 42
 | 
						|
  >>> mydata.color = 'red'
 | 
						|
 | 
						|
So, the separate thread:
 | 
						|
 | 
						|
  >>> thread = threading.Thread(target=f)
 | 
						|
  >>> thread.start()
 | 
						|
  >>> thread.join()
 | 
						|
 | 
						|
affects what we see:
 | 
						|
 | 
						|
  >>> mydata.number
 | 
						|
  11
 | 
						|
 | 
						|
>>> del mydata
 | 
						|
"""
 | 
						|
 | 
						|
from weakref import ref
 | 
						|
from contextlib import contextmanager
 | 
						|
 | 
						|
__all__ = ["local"]
 | 
						|
 | 
						|
# 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.
 | 
						|
 | 
						|
class _localimpl:
 | 
						|
    """A class managing thread-local dicts"""
 | 
						|
    __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        # 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 = '_threading_local._localimpl.' + str(id(self))
 | 
						|
        # { id(Thread) -> (ref(Thread), thread-local dict) }
 | 
						|
        self.dicts = {}
 | 
						|
 | 
						|
    def get_dict(self):
 | 
						|
        """Return the dict for the current thread. Raises KeyError if none
 | 
						|
        defined."""
 | 
						|
        thread = current_thread()
 | 
						|
        return self.dicts[id(thread)][1]
 | 
						|
 | 
						|
    def create_dict(self):
 | 
						|
        """Create a new dict for the current thread, and return it."""
 | 
						|
        localdict = {}
 | 
						|
        key = self.key
 | 
						|
        thread = current_thread()
 | 
						|
        idt = id(thread)
 | 
						|
        def local_deleted(_, key=key):
 | 
						|
            # When the localimpl is deleted, remove the thread attribute.
 | 
						|
            thread = wrthread()
 | 
						|
            if thread is not None:
 | 
						|
                del thread.__dict__[key]
 | 
						|
        def thread_deleted(_, idt=idt):
 | 
						|
            # 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.
 | 
						|
            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)
 | 
						|
        # We need to create the thread dict in anticipation of
 | 
						|
        # __init__ being called, to make sure we don't call it
 | 
						|
        # again ourselves.
 | 
						|
        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
 |