From 2ba63ab0fe02ed00ca18421c42aaeb87e1a67103 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 28 Mar 2025 14:22:52 +0100 Subject: [PATCH 1/4] gh-107006: Move `threading.local` docstring to docs --- Doc/library/threading.rst | 128 +++++++++++++++++++++++++++++++++++--- Lib/_threading_local.py | 122 ------------------------------------ 2 files changed, 119 insertions(+), 131 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 00511df32e4388..f3e93cbf0a69af 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -260,23 +260,133 @@ All of the methods described below are executed atomically. Thread-Local Data ----------------- -Thread-local data is data whose values are thread specific. To manage -thread-local data, just create an instance of :class:`local` (or a -subclass) and store attributes on it:: +Thread-local data is data whose values are thread specific. If you +have data that you want to be local to a thread, simply create a +:class:`local` object and use its attributes:: - mydata = threading.local() - mydata.x = 1 + >>> mydata = local() + >>> 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 + [] + +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 :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() 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: diff --git a/Lib/_threading_local.py b/Lib/_threading_local.py index b006d76c4e23df..0b9e5d3bbf6ef6 100644 --- a/Lib/_threading_local.py +++ b/Lib/_threading_local.py @@ -4,128 +4,6 @@ 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 - ... 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 52f92b5ebb3c3a97a2ac730a3ced0ebe2771e6a2 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 28 Mar 2025 18:26:05 +0100 Subject: [PATCH 2/4] Add news blurb --- .../2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst diff --git a/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst b/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst new file mode 100644 index 00000000000000..52b3358cc93f12 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst @@ -0,0 +1,2 @@ +Move documentation and example code for :class:`threading.local` from its +docstring to the official docs From c250bcda0f5f046dd2f0c9fc90d32d7ba927988f Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 28 Mar 2025 18:32:50 +0100 Subject: [PATCH 3/4] Address feedback --- Doc/library/threading.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index f3e93cbf0a69af..7385be304da89c 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -278,8 +278,7 @@ You can also access the :class:`local`-object's dictionary:: >>> 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:: +If we access the data in a different thread:: >>> log = [] >>> def f(): @@ -296,7 +295,7 @@ local to a thread. If we access the data in a different thread:: [[], 11] we get different data. Furthermore, changes made in the other thread -don't affect data seen in this thread: +don't affect data seen in this thread:: >>> mydata.number 42 From 63f54b11d50efad8ab0a75c49bb3253648a74dcf Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Mon, 5 May 2025 12:55:58 +0200 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/threading.rst | 2 +- .../2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 7385be304da89c..6309443c10f3be 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -261,7 +261,7 @@ Thread-Local Data ----------------- Thread-local data is data whose values are thread specific. If you -have data that you want to be local to a thread, simply create a +have data that you want to be local to a thread, create a :class:`local` object and use its attributes:: >>> mydata = local() diff --git a/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst b/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst index 52b3358cc93f12..eb55c2437f917f 100644 --- a/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst +++ b/Misc/NEWS.d/next/Documentation/2025-03-28-18-25-43.gh-issue-107006.BxFijD.rst @@ -1,2 +1,2 @@ Move documentation and example code for :class:`threading.local` from its -docstring to the official docs +docstring to the official docs.