bpo-41833: threading.Thread now uses the target name (GH-22357)

This commit is contained in:
Victor Stinner 2020-09-23 23:21:19 +02:00 committed by GitHub
parent 2e4dd336e5
commit 98c16c991d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 10 deletions

View File

@ -264,8 +264,10 @@ since it is impossible to detect the termination of alien threads.
*target* is the callable object to be invoked by the :meth:`run` method. *target* is the callable object to be invoked by the :meth:`run` method.
Defaults to ``None``, meaning nothing is called. Defaults to ``None``, meaning nothing is called.
*name* is the thread name. By default, a unique name is constructed of the *name* is the thread name. By default, a unique name is constructed
form "Thread-*N*" where *N* is a small decimal number. of the form "Thread-*N*" where *N* is a small decimal number,
or "Thread-*N* (target)" where "target" is ``target.__name__`` if the
*target* argument is specified.
*args* is the argument tuple for the target invocation. Defaults to ``()``. *args* is the argument tuple for the target invocation. Defaults to ``()``.
@ -280,6 +282,9 @@ since it is impossible to detect the termination of alien threads.
base class constructor (``Thread.__init__()``) before doing anything else to base class constructor (``Thread.__init__()``) before doing anything else to
the thread. the thread.
.. versionchanged:: 3.10
Use the *target* name if *name* argument is omitted.
.. versionchanged:: 3.3 .. versionchanged:: 3.3
Added the *daemon* argument. Added the *daemon* argument.

View File

@ -20,6 +20,7 @@ import subprocess
import signal import signal
import textwrap import textwrap
from unittest import mock
from test import lock_tests from test import lock_tests
from test import support from test import support
@ -86,6 +87,33 @@ class BaseTestCase(unittest.TestCase):
class ThreadTests(BaseTestCase): class ThreadTests(BaseTestCase):
@cpython_only
def test_name(self):
def func(): pass
thread = threading.Thread(name="myname1")
self.assertEqual(thread.name, "myname1")
# Convert int name to str
thread = threading.Thread(name=123)
self.assertEqual(thread.name, "123")
# target name is ignored if name is specified
thread = threading.Thread(target=func, name="myname2")
self.assertEqual(thread.name, "myname2")
with mock.patch.object(threading, '_counter', return_value=2):
thread = threading.Thread(name="")
self.assertEqual(thread.name, "Thread-2")
with mock.patch.object(threading, '_counter', return_value=3):
thread = threading.Thread()
self.assertEqual(thread.name, "Thread-3")
with mock.patch.object(threading, '_counter', return_value=5):
thread = threading.Thread(target=func)
self.assertEqual(thread.name, "Thread-5 (func)")
# Create a bunch of threads, let each do some work, wait until all are # Create a bunch of threads, let each do some work, wait until all are
# done. # done.
def test_various_ops(self): def test_various_ops(self):
@ -531,7 +559,7 @@ class ThreadTests(BaseTestCase):
import os, threading, sys import os, threading, sys
from test import support from test import support
def f(): def func():
pid = os.fork() pid = os.fork()
if pid == 0: if pid == 0:
main = threading.main_thread() main = threading.main_thread()
@ -544,14 +572,14 @@ class ThreadTests(BaseTestCase):
else: else:
support.wait_process(pid, exitcode=0) support.wait_process(pid, exitcode=0)
th = threading.Thread(target=f) th = threading.Thread(target=func)
th.start() th.start()
th.join() th.join()
""" """
_, out, err = assert_python_ok("-c", code) _, out, err = assert_python_ok("-c", code)
data = out.decode().replace('\r', '') data = out.decode().replace('\r', '')
self.assertEqual(err, b"") self.assertEqual(err, b"")
self.assertEqual(data, "Thread-1\nTrue\nTrue\n") self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n")
def test_main_thread_during_shutdown(self): def test_main_thread_during_shutdown(self):
# bpo-31516: current_thread() should still point to the main thread # bpo-31516: current_thread() should still point to the main thread

View File

@ -745,10 +745,9 @@ class BrokenBarrierError(RuntimeError):
# Helper to generate new thread names # Helper to generate new thread names
_counter = _count().__next__ _counter = _count(1).__next__
_counter() # Consume 0 so first non-main thread has id 1. def _newname(name_template):
def _newname(template="Thread-%d"): return name_template % _counter()
return template % _counter()
# Active thread administration # Active thread administration
_active_limbo_lock = _allocate_lock() _active_limbo_lock = _allocate_lock()
@ -800,8 +799,19 @@ class Thread:
assert group is None, "group argument must be None for now" assert group is None, "group argument must be None for now"
if kwargs is None: if kwargs is None:
kwargs = {} kwargs = {}
if name:
name = str(name)
else:
name = _newname("Thread-%d")
if target is not None:
try:
target_name = target.__name__
name += f" ({target_name})"
except AttributeError:
pass
self._target = target self._target = target
self._name = str(name or _newname()) self._name = name
self._args = args self._args = args
self._kwargs = kwargs self._kwargs = kwargs
if daemon is not None: if daemon is not None:

View File

@ -0,0 +1,2 @@
The :class:`threading.Thread` constructor now uses the target name if the
*target* argument is specified but the *name* argument is omitted.