mirror of https://github.com/python/cpython.git
gh-107006: Move `threading.local` docstring to docs (#131840)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
parent
8467026ed6
commit
b97328ef5d
|
@ -260,23 +260,132 @@ All of the methods described below are executed atomically.
|
||||||
Thread-Local Data
|
Thread-Local Data
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Thread-local data is data whose values are thread specific. To manage
|
Thread-local data is data whose values are thread specific. If you
|
||||||
thread-local data, just create an instance of :class:`local` (or a
|
have data that you want to be local to a thread, create a
|
||||||
subclass) and store attributes on it::
|
:class:`local` object and use its attributes::
|
||||||
|
|
||||||
mydata = threading.local()
|
>>> mydata = local()
|
||||||
mydata.x = 1
|
>>> mydata.number = 42
|
||||||
|
>>> mydata.number
|
||||||
|
42
|
||||||
|
|
||||||
The instance's values will be different for separate threads.
|
You can also access the :class:`local`-object's dictionary::
|
||||||
|
|
||||||
|
>>> mydata.__dict__
|
||||||
|
{'number': 42}
|
||||||
|
>>> mydata.__dict__.setdefault('widgets', [])
|
||||||
|
[]
|
||||||
|
>>> mydata.widgets
|
||||||
|
[]
|
||||||
|
|
||||||
|
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 :class:`local` object, including their
|
||||||
|
:attr:`~object.__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 :class:`local` objects by subclassing the
|
||||||
|
:class:`local` class::
|
||||||
|
|
||||||
|
>>> class MyLocal(local):
|
||||||
|
... number = 2
|
||||||
|
... def __init__(self, /, **kw):
|
||||||
|
... 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 :py:meth:`~object.__init__`
|
||||||
|
method, it will be called each time the :class:`local` object is used
|
||||||
|
in a separate thread. This is necessary to initialize each thread's
|
||||||
|
dictionary.
|
||||||
|
|
||||||
|
Now if we create a :class:`local` object::
|
||||||
|
|
||||||
|
>>> mydata = MyLocal(color='red')
|
||||||
|
|
||||||
|
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')], 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 :term:`__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
|
||||||
|
|
||||||
|
|
||||||
.. class:: local()
|
.. class:: local()
|
||||||
|
|
||||||
A class that represents thread-local data.
|
A class that represents thread-local data.
|
||||||
|
|
||||||
For more details and extensive examples, see the documentation string of the
|
|
||||||
:mod:`!_threading_local` module: :source:`Lib/_threading_local.py`.
|
|
||||||
|
|
||||||
|
|
||||||
.. _thread-objects:
|
.. _thread-objects:
|
||||||
|
|
||||||
|
|
|
@ -4,128 +4,6 @@
|
||||||
class. Depending on the version of Python you're using, there may be a
|
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
|
faster one available. You should always import the `local` class from
|
||||||
`threading`.)
|
`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
|
|
||||||
... def __init__(self, /, **kw):
|
|
||||||
... 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')], 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 weakref import ref
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Move documentation and example code for :class:`threading.local` from its
|
||||||
|
docstring to the official docs.
|
Loading…
Reference in New Issue