123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- """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__')
- current_thread().__dict__[key] = dict
- return self
- def _patch(self):
- key = object.__getattribute__(self, '_local__key')
- d = current_thread().__dict__.get(key)
- if d is None:
- d = {}
- current_thread().__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):
- if name == '__dict__':
- raise AttributeError(
- "%r object attribute '__dict__' is read-only"
- % self.__class__.__name__)
- lock = object.__getattribute__(self, '_local__lock')
- lock.acquire()
- try:
- _patch(self)
- return object.__setattr__(self, name, value)
- finally:
- lock.release()
- def __delattr__(self, name):
- if name == '__dict__':
- raise AttributeError(
- "%r object attribute '__dict__' is read-only"
- % self.__class__.__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:
- # We use the non-locking API since we might already hold the lock
- # (__del__ can be called at any point by the cyclic GC).
- threads = threading._enumerate()
- except:
- # If enumerating the current threads 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 current_thread, RLock
|