| 
									
										
										
										
											2006-04-21 10:40:58 +00:00
										 |  |  | """Thread-local objects.
 | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-04-21 10:40:58 +00:00
										 |  |  | (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`.) | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							| 
									
										
										
										
											2004-07-15 12:17:26 +00:00
										 |  |  | a thread-local object and use its attributes: | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   >>> 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(): | 
					
						
							| 
									
										
										
										
											2007-02-11 06:12:03 +00:00
										 |  |  |   ...     items = sorted(mydata.__dict__.items()) | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |   ...     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: | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |   >>> 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] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2004-07-15 12:17:26 +00:00
										 |  |  | without affecting this thread's data: | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   >>> 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 | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  | from weakref import ref | 
					
						
							|  |  |  | from contextlib import contextmanager | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-04-21 10:40:58 +00:00
										 |  |  | __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 | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  | # potential problems).  Note that all platforms on CPython do have support | 
					
						
							|  |  |  | # for locals in the `thread` module, and there is no circular import problem | 
					
						
							| 
									
										
										
										
											2006-04-21 10:40:58 +00:00
										 |  |  | # then, so problems introduced by fiddling the order of imports here won't | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  | # 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 | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  | class local: | 
					
						
							|  |  |  |     __slots__ = '_local__impl', '__dict__' | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |     def __new__(cls, *args, **kw): | 
					
						
							| 
									
										
										
										
											2010-02-22 19:55:46 +00:00
										 |  |  |         if (args or kw) and (cls.__init__ is object.__init__): | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |             raise TypeError("Initialization arguments are not supported") | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |         self = object.__new__(cls) | 
					
						
							|  |  |  |         impl = _localimpl() | 
					
						
							|  |  |  |         impl.localargs = (args, kw) | 
					
						
							|  |  |  |         impl.locallock = RLock() | 
					
						
							|  |  |  |         object.__setattr__(self, '_local__impl', impl) | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |         # We need to create the thread dict in anticipation of | 
					
						
							| 
									
										
										
										
											2005-11-25 02:02:50 +00:00
										 |  |  |         # __init__ being called, to make sure we don't call it | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |         # again ourselves. | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |         impl.create_dict() | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getattribute__(self, name): | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |         with _patch(self): | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |             return object.__getattribute__(self, name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __setattr__(self, name, value): | 
					
						
							| 
									
										
										
										
											2010-08-28 18:17:03 +00:00
										 |  |  |         if name == '__dict__': | 
					
						
							|  |  |  |             raise AttributeError( | 
					
						
							|  |  |  |                 "%r object attribute '__dict__' is read-only" | 
					
						
							|  |  |  |                 % self.__class__.__name__) | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |         with _patch(self): | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |             return object.__setattr__(self, name, value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __delattr__(self, name): | 
					
						
							| 
									
										
										
										
											2010-08-28 18:17:03 +00:00
										 |  |  |         if name == '__dict__': | 
					
						
							|  |  |  |             raise AttributeError( | 
					
						
							|  |  |  |                 "%r object attribute '__dict__' is read-only" | 
					
						
							|  |  |  |                 % self.__class__.__name__) | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  |         with _patch(self): | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  |             return object.__delattr__(self, name) | 
					
						
							| 
									
										
										
										
											2010-09-07 22:06:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2004-07-14 19:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-06-11 19:14:14 +00:00
										 |  |  | from threading import current_thread, RLock |