[Python-checkins] bpo-45735: Promise the long-time truth that `args=list` works (GH-30982)

tim-one webhook-mailer at python.org
Fri Feb 25 23:17:24 EST 2022


https://github.com/python/cpython/commit/e466faa9df9a1bd377d9725de5484471bc4af8d0
commit: e466faa9df9a1bd377d9725de5484471bc4af8d0
branch: main
author: Charlie Zhao <68189100+CharlieZhao95 at users.noreply.github.com>
committer: tim-one <tim.peters at gmail.com>
date: 2022-02-25T22:17:13-06:00
summary:

bpo-45735: Promise the long-time truth that `args=list` works (GH-30982)

For threads, and for multiprocessing, it's always been the case that ``args=list`` works fine when passed to ``Process()`` or ``Thread()``, and such code is common in the wild. But, according to the docs, only a tuple can be used. This brings the docs into synch with reality.

Doc changes by Charlie Zhao.
Co-authored-by: Tim Peters <tim.peters at gmail.com>

files:
M Doc/library/multiprocessing.rst
M Doc/library/threading.rst
M Lib/test/_test_multiprocessing.py
M Lib/test/test_threading.py
M Lib/threading.py
M Misc/ACKS

diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index cbbe1840fc351..ee40688781690 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -485,7 +485,9 @@ The :mod:`multiprocessing` package mostly replicates the API of the
    to ``True`` or ``False``.  If ``None`` (the default), this flag will be
    inherited from the creating process.
 
-   By default, no arguments are passed to *target*.
+   By default, no arguments are passed to *target*. The *args* argument,
+   which defaults to ``()``, can be used to specify a list or tuple of the arguments
+   to pass to *target*.
 
    If a subclass overrides the constructor, it must make sure it invokes the
    base class constructor (:meth:`Process.__init__`) before doing anything else
@@ -503,6 +505,19 @@ The :mod:`multiprocessing` package mostly replicates the API of the
       the target argument, if any, with sequential and keyword arguments taken
       from the *args* and *kwargs* arguments, respectively.
 
+      Using a list or tuple as the *args* argument passed to :class:`Process`
+      achieves the same effect.
+
+      Example::
+
+         >>> from multiprocessing import Process
+         >>> p = Process(target=print, args=[1])
+         >>> p.run()
+         1
+         >>> p = Process(target=print, args=(1,))
+         >>> p.run()
+         1
+
    .. method:: start()
 
       Start the process's activity.
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index 8c7664328a49d..2bcb72b6d4e50 100644
--- a/Doc/library/threading.rst
+++ b/Doc/library/threading.rst
@@ -314,7 +314,7 @@ since it is impossible to detect the termination of alien threads.
    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 a list or tuple of arguments for the target invocation.  Defaults to ``()``.
 
    *kwargs* is a dictionary of keyword arguments for the target invocation.
    Defaults to ``{}``.
@@ -353,6 +353,19 @@ since it is impossible to detect the termination of alien threads.
       the *target* argument, if any, with positional and keyword arguments taken
       from the *args* and *kwargs* arguments, respectively.
 
+      Using list or tuple as the *args* argument which passed to the :class:`Thread`
+      could achieve the same effect.
+
+      Example::
+
+         >>> from threading import Thread
+         >>> t = Thread(target=print, args=[1])
+         >>> t.run()
+         1
+         >>> t = Thread(target=print, args=(1,))
+         >>> t.run()
+         1
+
    .. method:: join(timeout=None)
 
       Wait until the thread terminates. This blocks the calling thread until
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index b2d656ab42897..6b1b1677910d1 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -247,6 +247,30 @@ def test_current(self):
         self.assertEqual(current.ident, os.getpid())
         self.assertEqual(current.exitcode, None)
 
+    def test_args_argument(self):
+        # bpo-45735: Using list or tuple as *args* in constructor could
+        # achieve the same effect.
+        args_cases = (1, "str", [1], (1,))
+        args_types = (list, tuple)
+
+        test_cases = itertools.product(args_cases, args_types)
+
+        for args, args_type in test_cases:
+            with self.subTest(args=args, args_type=args_type):
+                q = self.Queue(1)
+                # pass a tuple or list as args
+                p = self.Process(target=self._test_args, args=args_type((q, args)))
+                p.daemon = True
+                p.start()
+                child_args = q.get()
+                self.assertEqual(child_args, args)
+                p.join()
+                close_queue(q)
+
+    @classmethod
+    def _test_args(cls, q, arg):
+        q.put(arg)
+
     def test_daemon_argument(self):
         if self.TYPE == "threads":
             self.skipTest('test not appropriate for {}'.format(self.TYPE))
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 4830571474b5b..16c6934c6d432 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -123,6 +123,32 @@ def func(): pass
             thread = threading.Thread(target=func)
             self.assertEqual(thread.name, "Thread-5 (func)")
 
+    def test_args_argument(self):
+        # bpo-45735: Using list or tuple as *args* in constructor could
+        # achieve the same effect.
+        num_list = [1]
+        num_tuple = (1,)
+
+        str_list = ["str"]
+        str_tuple = ("str",)
+
+        list_in_tuple = ([1],)
+        tuple_in_list = [(1,)]
+
+        test_cases = (
+            (num_list, lambda arg: self.assertEqual(arg, 1)),
+            (num_tuple, lambda arg: self.assertEqual(arg, 1)),
+            (str_list, lambda arg: self.assertEqual(arg, "str")),
+            (str_tuple, lambda arg: self.assertEqual(arg, "str")),
+            (list_in_tuple, lambda arg: self.assertEqual(arg, [1])),
+            (tuple_in_list, lambda arg: self.assertEqual(arg, (1,)))
+        )
+
+        for args, target in test_cases:
+            with self.subTest(target=target, args=args):
+                t = threading.Thread(target=target, args=args)
+                t.start()
+
     @cpython_only
     def test_disallow_instantiation(self):
         # Ensure that the type disallows instantiation (bpo-43916)
diff --git a/Lib/threading.py b/Lib/threading.py
index 6068d06ab6c90..642f93e1eec31 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -852,7 +852,7 @@ class is implemented.
         *name* is the thread name. By default, a unique name is constructed of
         the form "Thread-N" where N is a small decimal number.
 
-        *args* is the argument tuple for the target invocation. Defaults to ().
+        *args* is a list or tuple of arguments for the target invocation. Defaults to ().
 
         *kwargs* is a dictionary of keyword arguments for the target
         invocation. Defaults to {}.
diff --git a/Misc/ACKS b/Misc/ACKS
index bab04b4613646..da2c82610d5ad 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -2004,6 +2004,7 @@ Yuxiao Zeng
 Uwe Zessin
 Cheng Zhang
 George Zhang
+Charlie Zhao
 Kai Zhu
 Tarek Ziadé
 Jelle Zijlstra



More information about the Python-checkins mailing list