Back to index

python3.2  3.2.2
_threading_local.py
Go to the documentation of this file.
00001 """Thread-local objects.
00002 
00003 (Note that this module provides a Python version of the threading.local
00004  class.  Depending on the version of Python you're using, there may be a
00005  faster one available.  You should always import the `local` class from
00006  `threading`.)
00007 
00008 Thread-local objects support the management of thread-local data.
00009 If you have data that you want to be local to a thread, simply create
00010 a thread-local object and use its attributes:
00011 
00012   >>> mydata = local()
00013   >>> mydata.number = 42
00014   >>> mydata.number
00015   42
00016 
00017 You can also access the local-object's dictionary:
00018 
00019   >>> mydata.__dict__
00020   {'number': 42}
00021   >>> mydata.__dict__.setdefault('widgets', [])
00022   []
00023   >>> mydata.widgets
00024   []
00025 
00026 What's important about thread-local objects is that their data are
00027 local to a thread. If we access the data in a different thread:
00028 
00029   >>> log = []
00030   >>> def f():
00031   ...     items = sorted(mydata.__dict__.items())
00032   ...     log.append(items)
00033   ...     mydata.number = 11
00034   ...     log.append(mydata.number)
00035 
00036   >>> import threading
00037   >>> thread = threading.Thread(target=f)
00038   >>> thread.start()
00039   >>> thread.join()
00040   >>> log
00041   [[], 11]
00042 
00043 we get different data.  Furthermore, changes made in the other thread
00044 don't affect data seen in this thread:
00045 
00046   >>> mydata.number
00047   42
00048 
00049 Of course, values you get from a local object, including a __dict__
00050 attribute, are for whatever thread was current at the time the
00051 attribute was read.  For that reason, you generally don't want to save
00052 these values across threads, as they apply only to the thread they
00053 came from.
00054 
00055 You can create custom local objects by subclassing the local class:
00056 
00057   >>> class MyLocal(local):
00058   ...     number = 2
00059   ...     initialized = False
00060   ...     def __init__(self, **kw):
00061   ...         if self.initialized:
00062   ...             raise SystemError('__init__ called too many times')
00063   ...         self.initialized = True
00064   ...         self.__dict__.update(kw)
00065   ...     def squared(self):
00066   ...         return self.number ** 2
00067 
00068 This can be useful to support default values, methods and
00069 initialization.  Note that if you define an __init__ method, it will be
00070 called each time the local object is used in a separate thread.  This
00071 is necessary to initialize each thread's dictionary.
00072 
00073 Now if we create a local object:
00074 
00075   >>> mydata = MyLocal(color='red')
00076 
00077 Now we have a default number:
00078 
00079   >>> mydata.number
00080   2
00081 
00082 an initial color:
00083 
00084   >>> mydata.color
00085   'red'
00086   >>> del mydata.color
00087 
00088 And a method that operates on the data:
00089 
00090   >>> mydata.squared()
00091   4
00092 
00093 As before, we can access the data in a separate thread:
00094 
00095   >>> log = []
00096   >>> thread = threading.Thread(target=f)
00097   >>> thread.start()
00098   >>> thread.join()
00099   >>> log
00100   [[('color', 'red'), ('initialized', True)], 11]
00101 
00102 without affecting this thread's data:
00103 
00104   >>> mydata.number
00105   2
00106   >>> mydata.color
00107   Traceback (most recent call last):
00108   ...
00109   AttributeError: 'MyLocal' object has no attribute 'color'
00110 
00111 Note that subclasses can define slots, but they are not thread
00112 local. They are shared across threads:
00113 
00114   >>> class MyLocal(local):
00115   ...     __slots__ = 'number'
00116 
00117   >>> mydata = MyLocal()
00118   >>> mydata.number = 42
00119   >>> mydata.color = 'red'
00120 
00121 So, the separate thread:
00122 
00123   >>> thread = threading.Thread(target=f)
00124   >>> thread.start()
00125   >>> thread.join()
00126 
00127 affects what we see:
00128 
00129   >>> mydata.number
00130   11
00131 
00132 >>> del mydata
00133 """
00134 
00135 from weakref import ref
00136 from contextlib import contextmanager
00137 
00138 __all__ = ["local"]
00139 
00140 # We need to use objects from the threading module, but the threading
00141 # module may also want to use our `local` class, if support for locals
00142 # isn't compiled in to the `thread` module.  This creates potential problems
00143 # with circular imports.  For that reason, we don't import `threading`
00144 # until the bottom of this file (a hack sufficient to worm around the
00145 # potential problems).  Note that all platforms on CPython do have support
00146 # for locals in the `thread` module, and there is no circular import problem
00147 # then, so problems introduced by fiddling the order of imports here won't
00148 # manifest.
00149 
00150 class _localimpl:
00151     """A class managing thread-local dicts"""
00152     __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
00153 
00154     def __init__(self):
00155         # The key used in the Thread objects' attribute dicts.
00156         # We keep it a string for speed but make it unlikely to clash with
00157         # a "real" attribute.
00158         self.key = '_threading_local._localimpl.' + str(id(self))
00159         # { id(Thread) -> (ref(Thread), thread-local dict) }
00160         self.dicts = {}
00161 
00162     def get_dict(self):
00163         """Return the dict for the current thread. Raises KeyError if none
00164         defined."""
00165         thread = current_thread()
00166         return self.dicts[id(thread)][1]
00167 
00168     def create_dict(self):
00169         """Create a new dict for the current thread, and return it."""
00170         localdict = {}
00171         key = self.key
00172         thread = current_thread()
00173         idt = id(thread)
00174         def local_deleted(_, key=key):
00175             # When the localimpl is deleted, remove the thread attribute.
00176             thread = wrthread()
00177             if thread is not None:
00178                 del thread.__dict__[key]
00179         def thread_deleted(_, idt=idt):
00180             # When the thread is deleted, remove the local dict.
00181             # Note that this is suboptimal if the thread object gets
00182             # caught in a reference loop. We would like to be called
00183             # as soon as the OS-level thread ends instead.
00184             local = wrlocal()
00185             if local is not None:
00186                 dct = local.dicts.pop(idt)
00187         wrlocal = ref(self, local_deleted)
00188         wrthread = ref(thread, thread_deleted)
00189         thread.__dict__[key] = wrlocal
00190         self.dicts[idt] = wrthread, localdict
00191         return localdict
00192 
00193 
00194 @contextmanager
00195 def _patch(self):
00196     impl = object.__getattribute__(self, '_local__impl')
00197     try:
00198         dct = impl.get_dict()
00199     except KeyError:
00200         dct = impl.create_dict()
00201         args, kw = impl.localargs
00202         self.__init__(*args, **kw)
00203     with impl.locallock:
00204         object.__setattr__(self, '__dict__', dct)
00205         yield
00206 
00207 
00208 class local:
00209     __slots__ = '_local__impl', '__dict__'
00210 
00211     def __new__(cls, *args, **kw):
00212         if (args or kw) and (cls.__init__ is object.__init__):
00213             raise TypeError("Initialization arguments are not supported")
00214         self = object.__new__(cls)
00215         impl = _localimpl()
00216         impl.localargs = (args, kw)
00217         impl.locallock = RLock()
00218         object.__setattr__(self, '_local__impl', impl)
00219         # We need to create the thread dict in anticipation of
00220         # __init__ being called, to make sure we don't call it
00221         # again ourselves.
00222         impl.create_dict()
00223         return self
00224 
00225     def __getattribute__(self, name):
00226         with _patch(self):
00227             return object.__getattribute__(self, name)
00228 
00229     def __setattr__(self, name, value):
00230         if name == '__dict__':
00231             raise AttributeError(
00232                 "%r object attribute '__dict__' is read-only"
00233                 % self.__class__.__name__)
00234         with _patch(self):
00235             return object.__setattr__(self, name, value)
00236 
00237     def __delattr__(self, name):
00238         if name == '__dict__':
00239             raise AttributeError(
00240                 "%r object attribute '__dict__' is read-only"
00241                 % self.__class__.__name__)
00242         with _patch(self):
00243             return object.__delattr__(self, name)
00244 
00245 
00246 from threading import current_thread, RLock