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