Skip to content

Commit

Permalink
bpo-36492: Deprecate passing some arguments as keyword arguments. (py…
Browse files Browse the repository at this point in the history
…thonGH-12637)

Deprecated passing the following arguments as keyword arguments:

- "func" in functools.partialmethod(), weakref.finalize(),
  profile.Profile.runcall(), cProfile.Profile.runcall(),
  bdb.Bdb.runcall(), trace.Trace.runfunc() and
  curses.wrapper().
- "function" in unittest.addModuleCleanup() and
  unittest.TestCase.addCleanup().
- "fn" in the submit() method of concurrent.futures.ThreadPoolExecutor
  and concurrent.futures.ProcessPoolExecutor.
- "callback" in contextlib.ExitStack.callback(),
  contextlib.AsyncExitStack.callback() and
  contextlib.AsyncExitStack.push_async_callback().
- "c" and "typeid" in the create() method of multiprocessing.managers.Server
  and multiprocessing.managers.SharedMemoryServer.
- "obj" in weakref.finalize().

Also allowed to pass arbitrary keyword arguments (even "self" and "func")
if the above arguments are passed as positional argument.
  • Loading branch information
serhiy-storchaka authored Apr 1, 2019
1 parent 5f2c508 commit 42a139e
Show file tree
Hide file tree
Showing 22 changed files with 457 additions and 21 deletions.
23 changes: 23 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,29 @@ Deprecated
version they will be errors.
(Contributed by Serhiy Storchaka in :issue:`36048`.)

* Deprecated passing the following arguments as keyword arguments:

- *func* in :func:`functools.partialmethod`, :func:`weakref.finalize`,
:meth:`profile.Profile.runcall`, :meth:`cProfile.Profile.runcall`,
:meth:`bdb.Bdb.runcall`, :meth:`trace.Trace.runfunc` and
:func:`curses.wrapper`.
- *function* in :func:`unittest.addModuleCleanup` and
:meth:`unittest.TestCase.addCleanup`.
- *fn* in the :meth:`~concurrent.futures.Executor.submit` method of
:class:`concurrent.futures.ThreadPoolExecutor` and
:class:`concurrent.futures.ProcessPoolExecutor`.
- *callback* in :meth:`contextlib.ExitStack.callback`,
:meth:`contextlib.AsyncExitStack.callback` and
:meth:`contextlib.AsyncExitStack.push_async_callback`.
- *c* and *typeid* in the :meth:`~multiprocessing.managers.Server.create`
method of :class:`multiprocessing.managers.Server` and
:class:`multiprocessing.managers.SharedMemoryServer`.
- *obj* in :func:`weakref.finalize`.

In future releases of Python they will be :ref:`positional-only
<positional-only_parameter>`.
(Contributed by Serhiy Storchaka in :issue:`36492`.)


API and Feature Removals
========================
Expand Down
17 changes: 16 additions & 1 deletion Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,26 @@ def runctx(self, cmd, globals, locals):

# This method is more useful to debug a single function call.

def runcall(self, func, *args, **kwds):
def runcall(*args, **kwds):
"""Debug a single function call.
Return the result of the function call.
"""
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor 'runcall' of 'Bdb' object "
"needs an argument")
elif 'func' in kwds:
func = kwds.pop('func')
self, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('runcall expected at least 1 positional argument, '
'got %d' % (len(args)-1))

self.reset()
sys.settrace(self.trace_dispatch)
res = None
Expand Down
17 changes: 16 additions & 1 deletion Lib/cProfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,22 @@ def runctx(self, cmd, globals, locals):
return self

# This method is more useful to profile a single function call.
def runcall(self, func, *args, **kw):
def runcall(*args, **kw):
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor 'runcall' of 'Profile' object "
"needs an argument")
elif 'func' in kw:
func = kw.pop('func')
self, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('runcall expected at least 1 positional argument, '
'got %d' % (len(args)-1))

self.enable()
try:
return func(*args, **kw)
Expand Down
15 changes: 14 additions & 1 deletion Lib/concurrent/futures/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def set_exception(self, exception):
class Executor(object):
"""This is an abstract base class for concrete asynchronous executors."""

def submit(self, fn, *args, **kwargs):
def submit(*args, **kwargs):
"""Submits a callable to be executed with the given arguments.
Schedules the callable to be executed as fn(*args, **kwargs) and returns
Expand All @@ -553,6 +553,19 @@ def submit(self, fn, *args, **kwargs):
Returns:
A Future representing the given call.
"""
if len(args) >= 2:
pass
elif not args:
raise TypeError("descriptor 'submit' of 'Executor' object "
"needs an argument")
elif 'fn' in kwargs:
import warnings
warnings.warn("Passing 'fn' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('submit expected at least 1 positional argument, '
'got %d' % (len(args)-1))

raise NotImplementedError()

def map(self, fn, *iterables, timeout=None, chunksize=1):
Expand Down
17 changes: 16 additions & 1 deletion Lib/concurrent/futures/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,22 @@ def _adjust_process_count(self):
p.start()
self._processes[p.pid] = p

def submit(self, fn, *args, **kwargs):
def submit(*args, **kwargs):
if len(args) >= 2:
self, fn, *args = args
elif not args:
raise TypeError("descriptor 'submit' of 'ProcessPoolExecutor' object "
"needs an argument")
elif 'fn' in kwargs:
fn = kwargs.pop('fn')
self, *args = args
import warnings
warnings.warn("Passing 'fn' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('submit expected at least 1 positional argument, '
'got %d' % (len(args)-1))

with self._shutdown_lock:
if self._broken:
raise BrokenProcessPool(self._broken)
Expand Down
17 changes: 16 additions & 1 deletion Lib/concurrent/futures/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,22 @@ def __init__(self, max_workers=None, thread_name_prefix='',
self._initializer = initializer
self._initargs = initargs

def submit(self, fn, *args, **kwargs):
def submit(*args, **kwargs):
if len(args) >= 2:
self, fn, *args = args
elif not args:
raise TypeError("descriptor 'submit' of 'ThreadPoolExecutor' object "
"needs an argument")
elif 'fn' in kwargs:
fn = kwargs.pop('fn')
self, *args = args
import warnings
warnings.warn("Passing 'fn' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('submit expected at least 1 positional argument, '
'got %d' % (len(args)-1))

with self._shutdown_lock:
if self._broken:
raise BrokenThreadPool(self._broken)
Expand Down
40 changes: 36 additions & 4 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ def _create_exit_wrapper(cm, cm_exit):
return MethodType(cm_exit, cm)

@staticmethod
def _create_cb_wrapper(callback, *args, **kwds):
def _create_cb_wrapper(*args, **kwds):
callback, *args = args
def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds)
return _exit_wrapper
Expand Down Expand Up @@ -426,11 +427,26 @@ def enter_context(self, cm):
self._push_cm_exit(cm, _exit)
return result

def callback(self, callback, *args, **kwds):
def callback(*args, **kwds):
"""Registers an arbitrary callback and arguments.
Cannot suppress exceptions.
"""
if len(args) >= 2:
self, callback, *args = args
elif not args:
raise TypeError("descriptor 'callback' of '_BaseExitStack' object "
"needs an argument")
elif 'callback' in kwds:
callback = kwds.pop('callback')
self, *args = args
import warnings
warnings.warn("Passing 'callback' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('callback expected at least 1 positional argument, '
'got %d' % (len(args)-1))

_exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)

# We changed the signature, so using @wraps is not appropriate, but
Expand Down Expand Up @@ -536,7 +552,8 @@ def _create_async_exit_wrapper(cm, cm_exit):
return MethodType(cm_exit, cm)

@staticmethod
def _create_async_cb_wrapper(callback, *args, **kwds):
def _create_async_cb_wrapper(*args, **kwds):
callback, *args = args
async def _exit_wrapper(exc_type, exc, tb):
await callback(*args, **kwds)
return _exit_wrapper
Expand Down Expand Up @@ -571,11 +588,26 @@ def push_async_exit(self, exit):
self._push_async_cm_exit(exit, exit_method)
return exit # Allow use as a decorator

def push_async_callback(self, callback, *args, **kwds):
def push_async_callback(*args, **kwds):
"""Registers an arbitrary coroutine function and arguments.
Cannot suppress exceptions.
"""
if len(args) >= 2:
self, callback, *args = args
elif not args:
raise TypeError("descriptor 'push_async_callback' of "
"'AsyncExitStack' object needs an argument")
elif 'callback' in kwds:
callback = kwds.pop('callback')
self, *args = args
import warnings
warnings.warn("Passing 'callback' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('push_async_callback expected at least 1 '
'positional argument, got %d' % (len(args)-1))

_exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)

# We changed the signature, so using @wraps is not appropriate, but
Expand Down
13 changes: 12 additions & 1 deletion Lib/curses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,25 @@ def start_color():
# raises an exception, wrapper() will restore the terminal to a sane state so
# you can read the resulting traceback.

def wrapper(func, *args, **kwds):
def wrapper(*args, **kwds):
"""Wrapper function that initializes curses and calls another function,
restoring normal keyboard/screen behavior on error.
The callable object 'func' is then passed the main window 'stdscr'
as its first argument, followed by any other arguments passed to
wrapper().
"""

if args:
func, *args = args
elif 'func' in kwds:
func = kwds.pop('func')
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('wrapper expected at least 1 positional argument, '
'got %d' % len(args))

try:
# Initialize curses
stdscr = initscr()
Expand Down
18 changes: 17 additions & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,23 @@ class partialmethod(object):
callables as instance methods.
"""

def __init__(self, func, *args, **keywords):
def __init__(*args, **keywords):
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor '__init__' of partialmethod "
"needs an argument")
elif 'func' in keywords:
func = keywords.pop('func')
self, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError("type 'partialmethod' takes at least one argument, "
"got %d" % (len(args)-1))
args = tuple(args)

if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor"
.format(func))
Expand Down
47 changes: 43 additions & 4 deletions Lib/multiprocessing/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,36 @@ def shutdown(self, c):
finally:
self.stop_event.set()

def create(self, c, typeid, *args, **kwds):
def create(*args, **kwds):
'''
Create a new shared object and return its id
'''
if len(args) >= 3:
self, c, typeid, *args = args
elif not args:
raise TypeError("descriptor 'create' of 'Server' object "
"needs an argument")
else:
if 'typeid' not in kwds:
raise TypeError('create expected at least 2 positional '
'arguments, got %d' % (len(args)-1))
typeid = kwds.pop('typeid')
if len(args) >= 2:
self, c, *args = args
import warnings
warnings.warn("Passing 'typeid' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
if 'c' not in kwds:
raise TypeError('create expected at least 2 positional '
'arguments, got %d' % (len(args)-1))
c = kwds.pop('c')
self, *args = args
import warnings
warnings.warn("Passing 'c' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
args = tuple(args)

with self.mutex:
callable, exposed, method_to_typeid, proxytype = \
self.registry[typeid]
Expand Down Expand Up @@ -583,10 +609,13 @@ def _run_server(cls, registry, address, authkey, serializer, writer,
util.info('manager serving at %r', server.address)
server.serve_forever()

def _create(self, typeid, *args, **kwds):
def _create(*args, **kwds):
'''
Create a new shared object; return the token and exposed tuple
'''
self, typeid, *args = args
args = tuple(args)

assert self._state.value == State.STARTED, 'server not yet started'
conn = self._Client(self._address, authkey=self._authkey)
try:
Expand Down Expand Up @@ -1261,15 +1290,25 @@ def __init__(self, *args, **kwargs):
_SharedMemoryTracker(f"shmm_{self.address}_{getpid()}")
util.debug(f"SharedMemoryServer started by pid {getpid()}")

def create(self, c, typeid, *args, **kwargs):
def create(*args, **kwargs):
"""Create a new distributed-shared object (not backed by a shared
memory block) and return its id to be used in a Proxy Object."""
# Unless set up as a shared proxy, don't make shared_memory_context
# a standard part of kwargs. This makes things easier for supplying
# simple functions.
if len(args) >= 3:
typeod = args[2]
elif 'typeid' in kwargs:
typeid = kwargs['typeid']
elif not args:
raise TypeError("descriptor 'create' of 'SharedMemoryServer' "
"object needs an argument")
else:
raise TypeError('create expected at least 2 positional '
'arguments, got %d' % (len(args)-1))
if hasattr(self.registry[typeid][-1], "_shared_memory_proxy"):
kwargs['shared_memory_context'] = self.shared_memory_context
return Server.create(self, c, typeid, *args, **kwargs)
return Server.create(*args, **kwargs)

def shutdown(self, c):
"Call unlink() on all tracked shared memory, terminate the Server."
Expand Down
17 changes: 16 additions & 1 deletion Lib/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,22 @@ def runctx(self, cmd, globals, locals):
return self

# This method is more useful to profile a single function call.
def runcall(self, func, *args, **kw):
def runcall(*args, **kw):
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor 'runcall' of 'Profile' object "
"needs an argument")
elif 'func' in kw:
func = kw.pop('func')
self, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('runcall expected at least 1 positional argument, '
'got %d' % (len(args)-1))

self.set_cmd(repr(func))
sys.setprofile(self.dispatcher)
try:
Expand Down
Loading

0 comments on commit 42a139e

Please sign in to comment.