mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	 e247e89846
			
		
	
	
		e247e89846
		
	
	
	
	
		
			
			bottom of the file. Restored that, and added a comment explaining why this is necessary. Hint: on my box, and yours, it's not :-( Also added an __all__ list.
		
			
				
	
	
		
			241 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
	
		
			6.8 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 = mydata.__dict__.items()
 | |
|   ...     items.sort()
 | |
|   ...     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
 | |
| """
 | |
| 
 | |
| __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 almost all platforms 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 on most boxes.
 | |
| 
 | |
| class _localbase(object):
 | |
|     __slots__ = '_local__key', '_local__args', '_local__lock'
 | |
| 
 | |
|     def __new__(cls, *args, **kw):
 | |
|         self = object.__new__(cls)
 | |
|         key = '_local__key', 'thread.local.' + str(id(self))
 | |
|         object.__setattr__(self, '_local__key', key)
 | |
|         object.__setattr__(self, '_local__args', (args, kw))
 | |
|         object.__setattr__(self, '_local__lock', RLock())
 | |
| 
 | |
|         if args or kw and (cls.__init__ is object.__init__):
 | |
|             raise TypeError("Initialization arguments are not supported")
 | |
| 
 | |
|         # We need to create the thread dict in anticipation of
 | |
|         # __init__ being called, to make sure we don't call it
 | |
|         # again ourselves.
 | |
|         dict = object.__getattribute__(self, '__dict__')
 | |
|         currentThread().__dict__[key] = dict
 | |
| 
 | |
|         return self
 | |
| 
 | |
| def _patch(self):
 | |
|     key = object.__getattribute__(self, '_local__key')
 | |
|     d = currentThread().__dict__.get(key)
 | |
|     if d is None:
 | |
|         d = {}
 | |
|         currentThread().__dict__[key] = d
 | |
|         object.__setattr__(self, '__dict__', d)
 | |
| 
 | |
|         # we have a new instance dict, so call out __init__ if we have
 | |
|         # one
 | |
|         cls = type(self)
 | |
|         if cls.__init__ is not object.__init__:
 | |
|             args, kw = object.__getattribute__(self, '_local__args')
 | |
|             cls.__init__(self, *args, **kw)
 | |
|     else:
 | |
|         object.__setattr__(self, '__dict__', d)
 | |
| 
 | |
| class local(_localbase):
 | |
| 
 | |
|     def __getattribute__(self, name):
 | |
|         lock = object.__getattribute__(self, '_local__lock')
 | |
|         lock.acquire()
 | |
|         try:
 | |
|             _patch(self)
 | |
|             return object.__getattribute__(self, name)
 | |
|         finally:
 | |
|             lock.release()
 | |
| 
 | |
|     def __setattr__(self, name, value):
 | |
|         lock = object.__getattribute__(self, '_local__lock')
 | |
|         lock.acquire()
 | |
|         try:
 | |
|             _patch(self)
 | |
|             return object.__setattr__(self, name, value)
 | |
|         finally:
 | |
|             lock.release()
 | |
| 
 | |
|     def __delattr__(self, name):
 | |
|         lock = object.__getattribute__(self, '_local__lock')
 | |
|         lock.acquire()
 | |
|         try:
 | |
|             _patch(self)
 | |
|             return object.__delattr__(self, name)
 | |
|         finally:
 | |
|             lock.release()
 | |
| 
 | |
|     def __del__(self):
 | |
|         import threading
 | |
| 
 | |
|         key = object.__getattribute__(self, '_local__key')
 | |
| 
 | |
|         try:
 | |
|             threads = list(threading.enumerate())
 | |
|         except:
 | |
|             # If enumerate fails, as it seems to do during
 | |
|             # shutdown, we'll skip cleanup under the assumption
 | |
|             # that there is nothing to clean up.
 | |
|             return
 | |
| 
 | |
|         for thread in threads:
 | |
|             try:
 | |
|                 __dict__ = thread.__dict__
 | |
|             except AttributeError:
 | |
|                 # Thread is dying, rest in peace.
 | |
|                 continue
 | |
| 
 | |
|             if key in __dict__:
 | |
|                 try:
 | |
|                     del __dict__[key]
 | |
|                 except KeyError:
 | |
|                     pass # didn't have anything in this thread
 | |
| 
 | |
| from threading import currentThread, RLock
 |