From edd53eaf3b1e3e6eaa18ec32d8437c04dd529e5d Mon Sep 17 00:00:00 2001 From: Romain Vavassori Date: Fri, 21 Aug 2026 14:20:22 +0200 Subject: [PATCH 1/3] gh-156131: threading: add run() and run_daemon() to start threads easily --- Doc/library/threading.rst | 11 +++++++++++ Lib/test/test_threading.py | 15 +++++++++++++++ Lib/threading.py | 16 +++++++++++++++- ...026-08-21-12-14-00.gh-issue-156131.KElflR.rst | 2 ++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-21-12-14-00.gh-issue-156131.KElflR.rst diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 5d9a7b6314b1668..22d2f95f6910ba0 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -220,6 +220,17 @@ 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. + + .. versionadded:: 3.16 + + .. function:: settrace(func) .. index:: single: trace function diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 96b43936be92cda..a6ac6dda7987079 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 abac31e25886fae..ad04c00dd42177c 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 000000000000000..29bd39f962915ea --- /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. From 4735ce76e9ad26b254b9e37a73be16bb139138d9 Mon Sep 17 00:00:00 2001 From: Romain Vavassori Date: Fri, 21 Aug 2026 17:40:13 +0200 Subject: [PATCH 2/3] gh-156131: add suggested modifications --- Doc/library/threading.rst | 22 +++++++++++++++++++--- Doc/whatsnew/3.16.rst | 8 ++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 22d2f95f6910ba0..d3ed6ccdb0d64e2 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -223,12 +223,28 @@ This module defines the following functions: .. function:: run(func, /, *args, **kwargs) run_daemon(func, /, *args, **kwargs) - Run `func(*args, **kwargs)` in a thread and return the corresponding + 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. + With :func:`run_daemon`, the thread is set as daemonic. - .. versionadded:: 3.16 + 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) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 063755e1eadcb53..14e871735d02365 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 ------- From da5a48a0b12b37434c5d9963d1efd3f064ad0098 Mon Sep 17 00:00:00 2001 From: Romain Vavassori Date: Wed, 26 Aug 2026 18:10:41 +0200 Subject: [PATCH 3/3] gh-156131: add config argument --- Doc/library/threading.rst | 12 ++++++--- Lib/test/test_threading.py | 13 ++++++++++ Lib/threading.py | 51 +++++++++++++++++++++++++++++++------- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index d3ed6ccdb0d64e2..3c804992d6ec58e 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -220,12 +220,15 @@ This module defines the following functions: .. versionadded:: 3.4 -.. function:: run(func, /, *args, **kwargs) - run_daemon(func, /, *args, **kwargs) +.. function:: run([config, ]func, /, *args, **kwargs) + run_daemon([config, ]func, /, *args, **kwargs) Run ``func(*args, **kwargs)`` in a thread and return the corresponding :class:`Thread` object. The thread is started automatically. + *config* is an optional dict which can be used to pass additional + arguments to the :class:`Thread` constructor. + With :func:`run_daemon`, the thread is set as daemonic. Example: @@ -240,10 +243,13 @@ This module defines the following functions: t1 = threading.run(fetch, 'https://example.com/') t2 = threading.run(fetch, 'https://example.com/post', data=payload) - t1.join() t2.join() + # with configuration + t = threading.run({'name': 'http-worker'}, fetch, 'https://example.com/') + t.join() + .. versionadded:: next diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index a6ac6dda7987079..c5df830b4aa265c 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1528,12 +1528,25 @@ def func(x, y): thread.join() self.assertEqual(z, [8]) + z = [] + thread = threading.run({'name': 'run-func'}, func, 5, y=3) + thread.join() + self.assertEqual(z, [8]) + self.assertEqual(thread.name, 'run-func') + z = [] thread = threading.run_daemon(func, 8, y=4) thread.join() self.assertEqual(z, [12]) self.assertEqual(thread.daemon, True) + z = [] + thread = threading.run_daemon({'name': 'run-func'}, func, 8, y=4) + thread.join() + self.assertEqual(z, [12]) + self.assertEqual(thread.daemon, True) + self.assertEqual(thread.name, 'run-func') + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): diff --git a/Lib/threading.py b/Lib/threading.py index ad04c00dd42177c..42f5eac01e1f011 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1665,19 +1665,52 @@ 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(x, /, *args, **kwargs): + """ + run(func, /, *args, **kwargs) -> Thread object + run(config, func, /, *args, **kwargs) -> Thread object + + Return a running thread of func(*args, **kwargs). -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 + *config* is an optional dict which can be used to pass additional + arguments to the Thread constructor. + + """ + if isinstance(x, dict): + if callable(x): + raise TypeError("ambiguous first argument: cannot determine if 'func' or 'config'") + if not args: + raise TypeError("missing positional argument 'func' after 'config'") + + config = x + func, *args = args + else: + config = {} + func = x + + thread = Thread(target=func, args=args, kwargs=kwargs, **config) thread.start() return thread +def run_daemon(x, /, *args, **kwargs): + """ + run_daemon(func, /, *args, **kwargs) -> Thread object + run_daemon(config, func, /, *args, **kwargs) -> Thread object + + Return a running daemonic thread of func(*args, **kwargs). + + *config* is an optional dict which can be used to pass additional + arguments to the Thread constructor. + + """ + if isinstance(x, dict): + if callable(x): + raise TypeError("ambiguous first argument: cannot determine if 'func' or 'config'") + + return run({'daemon': True} | x, *args, **kwargs) + else: + return run({'daemon': True}, x, *args, **kwargs) + _threading_atexits = [] _SHUTTING_DOWN = False