Show More
Commit Description:
Merge pull request #17 from nattee/master...
Commit Description:
Merge pull request #17 from nattee/master
upgrade to current working snapshot
References:
File last commit:
Show/Diff file:
Action:
lib/assets/Lib/_threading_local.py
| 246 lines
| 7.5 KiB
| text/x-python
| PythonLexer
|
r584 | """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 = sorted(mydata.__dict__.items()) | ||||
... 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 | ||||
""" | ||||
from weakref import ref | ||||
from contextlib import contextmanager | ||||
__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 all platforms on CPython 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. | ||||
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 | ||||
class local: | ||||
__slots__ = '_local__impl', '__dict__' | ||||
def __new__(cls, *args, **kw): | ||||
if (args or kw) and (cls.__init__ is object.__init__): | ||||
raise TypeError("Initialization arguments are not supported") | ||||
self = object.__new__(cls) | ||||
impl = _localimpl() | ||||
impl.localargs = (args, kw) | ||||
impl.locallock = RLock() | ||||
object.__setattr__(self, '_local__impl', impl) | ||||
# We need to create the thread dict in anticipation of | ||||
# __init__ being called, to make sure we don't call it | ||||
# again ourselves. | ||||
impl.create_dict() | ||||
return self | ||||
def __getattribute__(self, name): | ||||
with _patch(self): | ||||
return object.__getattribute__(self, name) | ||||
def __setattr__(self, name, value): | ||||
if name == '__dict__': | ||||
raise AttributeError( | ||||
"%r object attribute '__dict__' is read-only" | ||||
% self.__class__.__name__) | ||||
with _patch(self): | ||||
return object.__setattr__(self, name, value) | ||||
def __delattr__(self, name): | ||||
if name == '__dict__': | ||||
raise AttributeError( | ||||
"%r object attribute '__dict__' is read-only" | ||||
% self.__class__.__name__) | ||||
with _patch(self): | ||||
return object.__delattr__(self, name) | ||||
from threading import current_thread, RLock | ||||