diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 7fcf93d74610eb..7eb12fe116bd2d 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -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. Defaults to ``None``, meaning nothing is called. - *name* is the thread name. By default, a unique name is constructed of the - form "Thread-*N*" where *N* is a small decimal number. + *name* is the thread name. By default, a unique name is constructed + 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 ``()``. @@ -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 the thread. + .. versionchanged:: 3.10 + Use the *target* name if *name* argument is omitted. + .. versionchanged:: 3.3 Added the *daemon* argument. diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index d02d3b346f9cc8..2f0f3ae0946a57 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -20,6 +20,7 @@ import signal import textwrap +from unittest import mock from test import lock_tests from test import support @@ -86,6 +87,33 @@ def tearDown(self): 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 # done. def test_various_ops(self): @@ -531,7 +559,7 @@ def test_main_thread_after_fork_from_nonmain_thread(self): import os, threading, sys from test import support - def f(): + def func(): pid = os.fork() if pid == 0: main = threading.main_thread() @@ -544,14 +572,14 @@ def f(): else: support.wait_process(pid, exitcode=0) - th = threading.Thread(target=f) + th = threading.Thread(target=func) th.start() th.join() """ _, out, err = assert_python_ok("-c", code) data = out.decode().replace('\r', '') 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): # bpo-31516: current_thread() should still point to the main thread diff --git a/Lib/threading.py b/Lib/threading.py index ab29db77a747a2..06c77f70fe74f5 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -745,10 +745,9 @@ class BrokenBarrierError(RuntimeError): # Helper to generate new thread names -_counter = _count().__next__ -_counter() # Consume 0 so first non-main thread has id 1. -def _newname(template="Thread-%d"): - return template % _counter() +_counter = _count(1).__next__ +def _newname(name_template): + return name_template % _counter() # Active thread administration _active_limbo_lock = _allocate_lock() @@ -800,8 +799,19 @@ class is implemented. assert group is None, "group argument must be None for now" if kwargs is None: 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._name = str(name or _newname()) + self._name = name self._args = args self._kwargs = kwargs if daemon is not None: diff --git a/Misc/NEWS.d/next/Library/2020-09-22-13-51-14.bpo-41833.6HVDjT.rst b/Misc/NEWS.d/next/Library/2020-09-22-13-51-14.bpo-41833.6HVDjT.rst new file mode 100644 index 00000000000000..abb3a077d91b81 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-09-22-13-51-14.bpo-41833.6HVDjT.rst @@ -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.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: