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