Skip to content

Commit 98c16c9

Browse files
authored
bpo-41833: threading.Thread now uses the target name (GH-22357)
1 parent 2e4dd33 commit 98c16c9

File tree

4 files changed

+55
-10
lines changed

4 files changed

+55
-10
lines changed

Doc/library/threading.rst

+7-2
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,10 @@ since it is impossible to detect the termination of alien threads.
264264
*target* is the callable object to be invoked by the :meth:`run` method.
265265
Defaults to ``None``, meaning nothing is called.
266266

267-
*name* is the thread name. By default, a unique name is constructed of the
268-
form "Thread-*N*" where *N* is a small decimal number.
267+
*name* is the thread name. By default, a unique name is constructed
268+
of the form "Thread-*N*" where *N* is a small decimal number,
269+
or "Thread-*N* (target)" where "target" is ``target.__name__`` if the
270+
*target* argument is specified.
269271

270272
*args* is the argument tuple for the target invocation. Defaults to ``()``.
271273

@@ -280,6 +282,9 @@ since it is impossible to detect the termination of alien threads.
280282
base class constructor (``Thread.__init__()``) before doing anything else to
281283
the thread.
282284

285+
.. versionchanged:: 3.10
286+
Use the *target* name if *name* argument is omitted.
287+
283288
.. versionchanged:: 3.3
284289
Added the *daemon* argument.
285290

Lib/test/test_threading.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import signal
2121
import textwrap
2222

23+
from unittest import mock
2324
from test import lock_tests
2425
from test import support
2526

@@ -86,6 +87,33 @@ def tearDown(self):
8687

8788
class ThreadTests(BaseTestCase):
8889

90+
@cpython_only
91+
def test_name(self):
92+
def func(): pass
93+
94+
thread = threading.Thread(name="myname1")
95+
self.assertEqual(thread.name, "myname1")
96+
97+
# Convert int name to str
98+
thread = threading.Thread(name=123)
99+
self.assertEqual(thread.name, "123")
100+
101+
# target name is ignored if name is specified
102+
thread = threading.Thread(target=func, name="myname2")
103+
self.assertEqual(thread.name, "myname2")
104+
105+
with mock.patch.object(threading, '_counter', return_value=2):
106+
thread = threading.Thread(name="")
107+
self.assertEqual(thread.name, "Thread-2")
108+
109+
with mock.patch.object(threading, '_counter', return_value=3):
110+
thread = threading.Thread()
111+
self.assertEqual(thread.name, "Thread-3")
112+
113+
with mock.patch.object(threading, '_counter', return_value=5):
114+
thread = threading.Thread(target=func)
115+
self.assertEqual(thread.name, "Thread-5 (func)")
116+
89117
# Create a bunch of threads, let each do some work, wait until all are
90118
# done.
91119
def test_various_ops(self):
@@ -531,7 +559,7 @@ def test_main_thread_after_fork_from_nonmain_thread(self):
531559
import os, threading, sys
532560
from test import support
533561
534-
def f():
562+
def func():
535563
pid = os.fork()
536564
if pid == 0:
537565
main = threading.main_thread()
@@ -544,14 +572,14 @@ def f():
544572
else:
545573
support.wait_process(pid, exitcode=0)
546574
547-
th = threading.Thread(target=f)
575+
th = threading.Thread(target=func)
548576
th.start()
549577
th.join()
550578
"""
551579
_, out, err = assert_python_ok("-c", code)
552580
data = out.decode().replace('\r', '')
553581
self.assertEqual(err, b"")
554-
self.assertEqual(data, "Thread-1\nTrue\nTrue\n")
582+
self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n")
555583

556584
def test_main_thread_during_shutdown(self):
557585
# bpo-31516: current_thread() should still point to the main thread

Lib/threading.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -745,10 +745,9 @@ class BrokenBarrierError(RuntimeError):
745745

746746

747747
# Helper to generate new thread names
748-
_counter = _count().__next__
749-
_counter() # Consume 0 so first non-main thread has id 1.
750-
def _newname(template="Thread-%d"):
751-
return template % _counter()
748+
_counter = _count(1).__next__
749+
def _newname(name_template):
750+
return name_template % _counter()
752751

753752
# Active thread administration
754753
_active_limbo_lock = _allocate_lock()
@@ -800,8 +799,19 @@ class is implemented.
800799
assert group is None, "group argument must be None for now"
801800
if kwargs is None:
802801
kwargs = {}
802+
if name:
803+
name = str(name)
804+
else:
805+
name = _newname("Thread-%d")
806+
if target is not None:
807+
try:
808+
target_name = target.__name__
809+
name += f" ({target_name})"
810+
except AttributeError:
811+
pass
812+
803813
self._target = target
804-
self._name = str(name or _newname())
814+
self._name = name
805815
self._args = args
806816
self._kwargs = kwargs
807817
if daemon is not None:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :class:`threading.Thread` constructor now uses the target name if the
2+
*target* argument is specified but the *name* argument is omitted.

0 commit comments

Comments
 (0)