diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 5d9a7b6314b166..d3ed6ccdb0d64e 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -220,6 +220,33 @@ This module defines the following functions: .. versionadded:: 3.4 +.. function:: run(func, /, *args, **kwargs) + run_daemon(func, /, *args, **kwargs) + + Run ``func(*args, **kwargs)`` in a thread and return the corresponding + :class:`Thread` object. The thread is started automatically. + + With :func:`run_daemon`, the thread is set as daemonic. + + Example: + + .. code-block:: python + + import threading, urllib + + def fetch(url, data=None): + response = urllib.request.urlopen(url, data) + # further processing... + + t1 = threading.run(fetch, 'https://example.com/') + t2 = threading.run(fetch, 'https://example.com/post', data=payload) + + t1.join() + t2.join() + + .. versionadded:: next + + .. function:: settrace(func) .. index:: single: trace function diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 063755e1eadcb5..14e871735d0236 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -482,6 +482,14 @@ symtable (Contributed by Serhiy Storchaka in :gh:`153844`.) +threading +--------- + +* Add :func:`threading.run` and :func:`threading.run_daemon` + as a convenient way to start threads. + (Contributed by Romain Vavassori in :gh:`156131`.) + + tkinter ------- diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 96b43936be92cd..a6ac6dda798707 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1519,6 +1519,21 @@ def run_in_bg(): self.assertEqual(err, b"") self.assertEqual(out.strip(), b"Exiting...") + def test_run(self): + def func(x, y): + z.append(x + y) + + z = [] + thread = threading.run(func, 5, y=3) + thread.join() + self.assertEqual(z, [8]) + + z = [] + thread = threading.run_daemon(func, 8, y=4) + thread.join() + self.assertEqual(z, [12]) + self.assertEqual(thread.daemon, True) + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): diff --git a/Lib/threading.py b/Lib/threading.py index abac31e25886fa..ad04c00dd42177 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -30,7 +30,8 @@ 'setprofile', 'settrace', 'local', 'stack_size', 'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile', 'serialize_iterator', 'synchronized_iterator', 'concurrent_tee', - 'setprofile_all_threads','settrace_all_threads'] + 'setprofile_all_threads','settrace_all_threads', + 'run', 'run_daemon'] # Rename some stuff so "from threading import *" is safe _start_joinable_thread = _thread.start_joinable_thread @@ -1664,6 +1665,19 @@ def enumerate(): with _active_limbo_lock: return list(_active.values()) + list(_limbo.values()) +def run(func, /, *args, **kwargs): + """Return a running Thread object of func(*args, **kwargs).""" + thread = Thread(target=func, name=func.__name__, args=args, kwargs=kwargs) + thread.start() + return thread + +def run_daemon(func, /, *args, **kwargs): + """Return a running daemonic Thread object of func(*args, **kwargs).""" + thread = Thread(target=func, name=func.__name__, args=args, kwargs=kwargs) + thread.daemon = True + thread.start() + return thread + _threading_atexits = [] _SHUTTING_DOWN = False diff --git a/Misc/NEWS.d/next/Library/2026-08-21-12-14-00.gh-issue-156131.KElflR.rst b/Misc/NEWS.d/next/Library/2026-08-21-12-14-00.gh-issue-156131.KElflR.rst new file mode 100644 index 00000000000000..29bd39f962915e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-21-12-14-00.gh-issue-156131.KElflR.rst @@ -0,0 +1,2 @@ +Add :func:`threading.run` and :func:`threading.run_daemon` functions as +a convenient way to start threads.