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@gmail.com>
This commit is contained in:
Charlie Zhao 2022-02-26 12:17:13 +08:00 committed by GitHub
parent 5ab745fc51
commit e466faa9df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 3 deletions

View file

@ -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 to ``True`` or ``False``. If ``None`` (the default), this flag will be
inherited from the creating process. 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 If a subclass overrides the constructor, it must make sure it invokes the
base class constructor (:meth:`Process.__init__`) before doing anything else 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 the target argument, if any, with sequential and keyword arguments taken
from the *args* and *kwargs* arguments, respectively. 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() .. method:: start()
Start the process's activity. Start the process's activity.

View file

@ -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 or "Thread-*N* (target)" where "target" is ``target.__name__`` if the
*target* argument is specified. *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. *kwargs* is a dictionary of keyword arguments for the target invocation.
Defaults to ``{}``. 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 the *target* argument, if any, with positional and keyword arguments taken
from the *args* and *kwargs* arguments, respectively. 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) .. method:: join(timeout=None)
Wait until the thread terminates. This blocks the calling thread until Wait until the thread terminates. This blocks the calling thread until

View file

@ -247,6 +247,30 @@ class _TestProcess(BaseTestCase):
self.assertEqual(current.ident, os.getpid()) self.assertEqual(current.ident, os.getpid())
self.assertEqual(current.exitcode, None) 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): def test_daemon_argument(self):
if self.TYPE == "threads": if self.TYPE == "threads":
self.skipTest('test not appropriate for {}'.format(self.TYPE)) self.skipTest('test not appropriate for {}'.format(self.TYPE))

View file

@ -123,6 +123,32 @@ class ThreadTests(BaseTestCase):
thread = threading.Thread(target=func) thread = threading.Thread(target=func)
self.assertEqual(thread.name, "Thread-5 (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 @cpython_only
def test_disallow_instantiation(self): def test_disallow_instantiation(self):
# Ensure that the type disallows instantiation (bpo-43916) # Ensure that the type disallows instantiation (bpo-43916)

View file

@ -852,7 +852,7 @@ class Thread:
*name* is the thread name. By default, a unique name is constructed of *name* is the thread name. By default, a unique name is constructed of
the form "Thread-N" where N is a small decimal number. 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 *kwargs* is a dictionary of keyword arguments for the target
invocation. Defaults to {}. invocation. Defaults to {}.

View file

@ -2004,6 +2004,7 @@ Yuxiao Zeng
Uwe Zessin Uwe Zessin
Cheng Zhang Cheng Zhang
George Zhang George Zhang
Charlie Zhao
Kai Zhu Kai Zhu
Tarek Ziadé Tarek Ziadé
Jelle Zijlstra Jelle Zijlstra