bpo-36492: Deprecate passing some arguments as keyword arguments. (GH-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.
This commit is contained in:
Serhiy Storchaka 2019-04-01 09:16:35 +03:00 committed by GitHub
parent 5f2c50810a
commit 42a139ed88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 457 additions and 21 deletions

View File

@ -594,6 +594,29 @@ Deprecated
version they will be errors. version they will be errors.
(Contributed by Serhiy Storchaka in :issue:`36048`.) (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 API and Feature Removals
======================== ========================

View File

@ -618,11 +618,26 @@ class Bdb:
# This method is more useful to debug a single function call. # 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. """Debug a single function call.
Return the result of the 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() self.reset()
sys.settrace(self.trace_dispatch) sys.settrace(self.trace_dispatch)
res = None res = None

View File

@ -103,7 +103,22 @@ class Profile(_lsprof.Profiler):
return self return self
# This method is more useful to profile a single function call. # 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() self.enable()
try: try:
return func(*args, **kw) return func(*args, **kw)

View File

@ -544,7 +544,7 @@ class Future(object):
class Executor(object): class Executor(object):
"""This is an abstract base class for concrete asynchronous executors.""" """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. """Submits a callable to be executed with the given arguments.
Schedules the callable to be executed as fn(*args, **kwargs) and returns Schedules the callable to be executed as fn(*args, **kwargs) and returns
@ -553,6 +553,19 @@ class Executor(object):
Returns: Returns:
A Future representing the given call. 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() raise NotImplementedError()
def map(self, fn, *iterables, timeout=None, chunksize=1): def map(self, fn, *iterables, timeout=None, chunksize=1):

View File

@ -594,7 +594,22 @@ class ProcessPoolExecutor(_base.Executor):
p.start() p.start()
self._processes[p.pid] = p 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: with self._shutdown_lock:
if self._broken: if self._broken:
raise BrokenProcessPool(self._broken) raise BrokenProcessPool(self._broken)

View File

@ -142,7 +142,22 @@ class ThreadPoolExecutor(_base.Executor):
self._initializer = initializer self._initializer = initializer
self._initargs = initargs 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: with self._shutdown_lock:
if self._broken: if self._broken:
raise BrokenThreadPool(self._broken) raise BrokenThreadPool(self._broken)

View File

@ -377,7 +377,8 @@ class _BaseExitStack:
return MethodType(cm_exit, cm) return MethodType(cm_exit, cm)
@staticmethod @staticmethod
def _create_cb_wrapper(callback, *args, **kwds): def _create_cb_wrapper(*args, **kwds):
callback, *args = args
def _exit_wrapper(exc_type, exc, tb): def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds) callback(*args, **kwds)
return _exit_wrapper return _exit_wrapper
@ -426,11 +427,26 @@ class _BaseExitStack:
self._push_cm_exit(cm, _exit) self._push_cm_exit(cm, _exit)
return result return result
def callback(self, callback, *args, **kwds): def callback(*args, **kwds):
"""Registers an arbitrary callback and arguments. """Registers an arbitrary callback and arguments.
Cannot suppress exceptions. 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) _exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
# We changed the signature, so using @wraps is not appropriate, but # We changed the signature, so using @wraps is not appropriate, but
@ -536,7 +552,8 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
return MethodType(cm_exit, cm) return MethodType(cm_exit, cm)
@staticmethod @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): async def _exit_wrapper(exc_type, exc, tb):
await callback(*args, **kwds) await callback(*args, **kwds)
return _exit_wrapper return _exit_wrapper
@ -571,11 +588,26 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
self._push_async_cm_exit(exit, exit_method) self._push_async_cm_exit(exit, exit_method)
return exit # Allow use as a decorator 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. """Registers an arbitrary coroutine function and arguments.
Cannot suppress exceptions. 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) _exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
# We changed the signature, so using @wraps is not appropriate, but # We changed the signature, so using @wraps is not appropriate, but

View File

@ -60,7 +60,7 @@ except NameError:
# raises an exception, wrapper() will restore the terminal to a sane state so # raises an exception, wrapper() will restore the terminal to a sane state so
# you can read the resulting traceback. # you can read the resulting traceback.
def wrapper(func, *args, **kwds): def wrapper(*args, **kwds):
"""Wrapper function that initializes curses and calls another function, """Wrapper function that initializes curses and calls another function,
restoring normal keyboard/screen behavior on error. restoring normal keyboard/screen behavior on error.
The callable object 'func' is then passed the main window 'stdscr' The callable object 'func' is then passed the main window 'stdscr'
@ -68,6 +68,17 @@ def wrapper(func, *args, **kwds):
wrapper(). 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: try:
# Initialize curses # Initialize curses
stdscr = initscr() stdscr = initscr()

View File

@ -354,7 +354,23 @@ class partialmethod(object):
callables as instance methods. 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__"): if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor" raise TypeError("{!r} is not callable or a descriptor"
.format(func)) .format(func))

View File

@ -358,10 +358,36 @@ class Server(object):
finally: finally:
self.stop_event.set() self.stop_event.set()
def create(self, c, typeid, *args, **kwds): def create(*args, **kwds):
''' '''
Create a new shared object and return its id 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: with self.mutex:
callable, exposed, method_to_typeid, proxytype = \ callable, exposed, method_to_typeid, proxytype = \
self.registry[typeid] self.registry[typeid]
@ -583,10 +609,13 @@ class BaseManager(object):
util.info('manager serving at %r', server.address) util.info('manager serving at %r', server.address)
server.serve_forever() server.serve_forever()
def _create(self, typeid, *args, **kwds): def _create(*args, **kwds):
''' '''
Create a new shared object; return the token and exposed tuple 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' assert self._state.value == State.STARTED, 'server not yet started'
conn = self._Client(self._address, authkey=self._authkey) conn = self._Client(self._address, authkey=self._authkey)
try: try:
@ -1261,15 +1290,25 @@ if HAS_SHMEM:
_SharedMemoryTracker(f"shmm_{self.address}_{getpid()}") _SharedMemoryTracker(f"shmm_{self.address}_{getpid()}")
util.debug(f"SharedMemoryServer started by pid {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 """Create a new distributed-shared object (not backed by a shared
memory block) and return its id to be used in a Proxy Object.""" 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 # Unless set up as a shared proxy, don't make shared_memory_context
# a standard part of kwargs. This makes things easier for supplying # a standard part of kwargs. This makes things easier for supplying
# simple functions. # 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"): if hasattr(self.registry[typeid][-1], "_shared_memory_proxy"):
kwargs['shared_memory_context'] = self.shared_memory_context 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): def shutdown(self, c):
"Call unlink() on all tracked shared memory, terminate the Server." "Call unlink() on all tracked shared memory, terminate the Server."

View File

@ -425,7 +425,22 @@ class Profile:
return self return self
# This method is more useful to profile a single function call. # 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)) self.set_cmd(repr(func))
sys.setprofile(self.dispatcher) sys.setprofile(self.dispatcher)
try: try:

View File

@ -49,6 +49,9 @@ INITIALIZER_STATUS = 'uninitialized'
def mul(x, y): def mul(x, y):
return x * y return x * y
def capture(*args, **kwargs):
return args, kwargs
def sleep_and_raise(t): def sleep_and_raise(t):
time.sleep(t) time.sleep(t)
raise Exception('this is an exception') raise Exception('this is an exception')
@ -658,6 +661,13 @@ class ExecutorTest:
def test_submit_keyword(self): def test_submit_keyword(self):
future = self.executor.submit(mul, 2, y=8) future = self.executor.submit(mul, 2, y=8)
self.assertEqual(16, future.result()) self.assertEqual(16, future.result())
future = self.executor.submit(capture, 1, self=2, fn=3)
self.assertEqual(future.result(), ((1,), {'self': 2, 'fn': 3}))
with self.assertWarns(DeprecationWarning):
future = self.executor.submit(fn=capture, arg=1)
self.assertEqual(future.result(), ((), {'arg': 1}))
with self.assertRaises(TypeError):
self.executor.submit(arg=1)
def test_map(self): def test_map(self):
self.assertEqual( self.assertEqual(

View File

@ -574,6 +574,7 @@ class TestBaseExitStack:
((), dict(example=1)), ((), dict(example=1)),
((1,), dict(example=1)), ((1,), dict(example=1)),
((1,2), dict(example=1)), ((1,2), dict(example=1)),
((1,2), dict(self=3, callback=4)),
] ]
result = [] result = []
def _exit(*args, **kwds): def _exit(*args, **kwds):
@ -596,6 +597,16 @@ class TestBaseExitStack:
self.assertIsNone(wrapper[1].__doc__, _exit.__doc__) self.assertIsNone(wrapper[1].__doc__, _exit.__doc__)
self.assertEqual(result, expected) self.assertEqual(result, expected)
result = []
with self.exit_stack() as stack:
with self.assertRaises(TypeError):
stack.callback(arg=1)
with self.assertRaises(TypeError):
self.exit_stack.callback(arg=2)
with self.assertWarns(DeprecationWarning):
stack.callback(callback=_exit, arg=3)
self.assertEqual(result, [((), {'arg': 3})])
def test_push(self): def test_push(self):
exc_raised = ZeroDivisionError exc_raised = ZeroDivisionError
def _expect_exc(exc_type, exc, exc_tb): def _expect_exc(exc_type, exc, exc_tb):

View File

@ -352,6 +352,16 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
self.assertEqual(result, expected) self.assertEqual(result, expected)
result = []
async with AsyncExitStack() as stack:
with self.assertRaises(TypeError):
stack.push_async_callback(arg=1)
with self.assertRaises(TypeError):
self.exit_stack.push_async_callback(arg=2)
with self.assertWarns(DeprecationWarning):
stack.push_async_callback(callback=_exit, arg=3)
self.assertEqual(result, [((), {'arg': 3})])
@_async_test @_async_test
async def test_async_push(self): async def test_async_push(self):
exc_raised = ZeroDivisionError exc_raised = ZeroDivisionError

View File

@ -464,6 +464,7 @@ class TestPartialMethod(unittest.TestCase):
positional = functools.partialmethod(capture, 1) positional = functools.partialmethod(capture, 1)
keywords = functools.partialmethod(capture, a=2) keywords = functools.partialmethod(capture, a=2)
both = functools.partialmethod(capture, 3, b=4) both = functools.partialmethod(capture, 3, b=4)
spec_keywords = functools.partialmethod(capture, self=1, func=2)
nested = functools.partialmethod(positional, 5) nested = functools.partialmethod(positional, 5)
@ -497,6 +498,8 @@ class TestPartialMethod(unittest.TestCase):
self.assertEqual(self.A.both(self.a, 5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6})) self.assertEqual(self.A.both(self.a, 5, c=6), ((self.a, 3, 5), {'b': 4, 'c': 6}))
self.assertEqual(self.a.spec_keywords(), ((self.a,), {'self': 1, 'func': 2}))
def test_nested(self): def test_nested(self):
self.assertEqual(self.a.nested(), ((self.a, 1, 5), {})) self.assertEqual(self.a.nested(), ((self.a, 1, 5), {}))
self.assertEqual(self.a.nested(6), ((self.a, 1, 5, 6), {})) self.assertEqual(self.a.nested(6), ((self.a, 1, 5, 6), {}))
@ -550,6 +553,14 @@ class TestPartialMethod(unittest.TestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
class B(object): class B(object):
method = functools.partialmethod(None, 1) method = functools.partialmethod(None, 1)
with self.assertRaises(TypeError):
class B:
method = functools.partialmethod()
with self.assertWarns(DeprecationWarning):
class B:
method = functools.partialmethod(func=capture, a=1)
b = B()
self.assertEqual(b.method(2, x=3), ((b, 2), {'a': 1, 'x': 3}))
def test_repr(self): def test_repr(self):
self.assertEqual(repr(vars(self.A)['both']), self.assertEqual(repr(vars(self.A)['both']),

View File

@ -70,6 +70,9 @@ def traced_func_calling_generator():
def traced_doubler(num): def traced_doubler(num):
return num * 2 return num * 2
def traced_capturer(*args, **kwargs):
return args, kwargs
def traced_caller_list_comprehension(): def traced_caller_list_comprehension():
k = 10 k = 10
mylist = [traced_doubler(i) for i in range(k)] mylist = [traced_doubler(i) for i in range(k)]
@ -270,6 +273,15 @@ class TestFuncs(unittest.TestCase):
} }
self.assertEqual(self.tracer.results().calledfuncs, expected) self.assertEqual(self.tracer.results().calledfuncs, expected)
def test_arg_errors(self):
res = self.tracer.runfunc(traced_capturer, 1, 2, self=3, func=4)
self.assertEqual(res, ((1, 2), {'self': 3, 'func': 4}))
with self.assertWarns(DeprecationWarning):
res = self.tracer.runfunc(func=traced_capturer, arg=1)
self.assertEqual(res, ((), {'arg': 1}))
with self.assertRaises(TypeError):
self.tracer.runfunc()
def test_loop_caller_importing(self): def test_loop_caller_importing(self):
self.tracer.runfunc(traced_func_importing_caller, 1) self.tracer.runfunc(traced_func_importing_caller, 1)

View File

@ -1839,6 +1839,35 @@ class FinalizeTestCase(unittest.TestCase):
self.assertEqual(f.alive, False) self.assertEqual(f.alive, False)
self.assertEqual(res, [199]) self.assertEqual(res, [199])
def test_arg_errors(self):
def fin(*args, **kwargs):
res.append((args, kwargs))
a = self.A()
res = []
f = weakref.finalize(a, fin, 1, 2, func=3, obj=4)
self.assertEqual(f.peek(), (a, fin, (1, 2), {'func': 3, 'obj': 4}))
f()
self.assertEqual(res, [((1, 2), {'func': 3, 'obj': 4})])
res = []
with self.assertWarns(DeprecationWarning):
f = weakref.finalize(a, func=fin, arg=1)
self.assertEqual(f.peek(), (a, fin, (), {'arg': 1}))
f()
self.assertEqual(res, [((), {'arg': 1})])
res = []
with self.assertWarns(DeprecationWarning):
f = weakref.finalize(obj=a, func=fin, arg=1)
self.assertEqual(f.peek(), (a, fin, (), {'arg': 1}))
f()
self.assertEqual(res, [((), {'arg': 1})])
self.assertRaises(TypeError, weakref.finalize, a)
self.assertRaises(TypeError, weakref.finalize)
def test_order(self): def test_order(self):
a = self.A() a = self.A()
res = [] res = []

View File

@ -451,7 +451,22 @@ class Trace:
sys.settrace(None) sys.settrace(None)
threading.settrace(None) threading.settrace(None)
def runfunc(self, func, *args, **kw): def runfunc(*args, **kw):
if len(args) >= 2:
self, func, *args = args
elif not args:
raise TypeError("descriptor 'runfunc' of 'Trace' 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('runfunc expected at least 1 positional argument, '
'got %d' % (len(args)-1))
result = None result = None
if not self.donothing: if not self.donothing:
sys.settrace(self.globaltrace) sys.settrace(self.globaltrace)

View File

@ -86,9 +86,21 @@ def _id(obj):
_module_cleanups = [] _module_cleanups = []
def addModuleCleanup(function, *args, **kwargs): def addModuleCleanup(*args, **kwargs):
"""Same as addCleanup, except the cleanup items are called even if """Same as addCleanup, except the cleanup items are called even if
setUpModule fails (unlike tearDownModule).""" setUpModule fails (unlike tearDownModule)."""
if args:
function, *args = args
elif 'function' in kwargs:
function = kwargs.pop('function')
import warnings
warnings.warn("Passing 'function' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('addModuleCleanup expected at least 1 positional '
'argument, got %d' % (len(args)-1))
args = tuple(args)
_module_cleanups.append((function, args, kwargs)) _module_cleanups.append((function, args, kwargs))
@ -463,18 +475,44 @@ class TestCase(object):
""" """
self._type_equality_funcs[typeobj] = function self._type_equality_funcs[typeobj] = function
def addCleanup(self, function, *args, **kwargs): def addCleanup(*args, **kwargs):
"""Add a function, with arguments, to be called when the test is """Add a function, with arguments, to be called when the test is
completed. Functions added are called on a LIFO basis and are completed. Functions added are called on a LIFO basis and are
called after tearDown on test failure or success. called after tearDown on test failure or success.
Cleanup items are called even if setUp fails (unlike tearDown).""" Cleanup items are called even if setUp fails (unlike tearDown)."""
if len(args) >= 2:
self, function, *args = args
elif not args:
raise TypeError("descriptor 'addCleanup' of 'TestCase' object "
"needs an argument")
elif 'function' in kwargs:
function = kwargs.pop('function')
self, *args = args
import warnings
warnings.warn("Passing 'function' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
raise TypeError('addCleanup expected at least 1 positional '
'argument, got %d' % (len(args)-1))
args = tuple(args)
self._cleanups.append((function, args, kwargs)) self._cleanups.append((function, args, kwargs))
@classmethod @classmethod
def addClassCleanup(cls, function, *args, **kwargs): def addClassCleanup(*args, **kwargs):
"""Same as addCleanup, except the cleanup items are called even if """Same as addCleanup, except the cleanup items are called even if
setUpClass fails (unlike tearDownClass).""" setUpClass fails (unlike tearDownClass)."""
if len(args) >= 2:
cls, function, *args = args
elif not args:
raise TypeError("descriptor 'addClassCleanup' of 'TestCase' object "
"needs an argument")
else:
raise TypeError('addClassCleanup expected at least 1 positional '
'argument, got %d' % (len(args)-1))
args = tuple(args)
cls._class_cleanups.append((function, args, kwargs)) cls._class_cleanups.append((function, args, kwargs))
def setUp(self): def setUp(self):

View File

@ -403,6 +403,22 @@ class TestModuleCleanUp(unittest.TestCase):
self.assertEqual(str(e.exception), 'CleanUpExc') self.assertEqual(str(e.exception), 'CleanUpExc')
self.assertEqual(unittest.case._module_cleanups, []) self.assertEqual(unittest.case._module_cleanups, [])
def test_addModuleCleanup_arg_errors(self):
cleanups = []
def cleanup(*args, **kwargs):
cleanups.append((args, kwargs))
class Module(object):
unittest.addModuleCleanup(cleanup, 1, 2, function='hello')
with self.assertWarns(DeprecationWarning):
unittest.addModuleCleanup(function=cleanup, arg='hello')
with self.assertRaises(TypeError):
unittest.addModuleCleanup()
unittest.case.doModuleCleanups()
self.assertEqual(cleanups,
[((), {'arg': 'hello'}),
((1, 2), {'function': 'hello'})])
def test_run_module_cleanUp(self): def test_run_module_cleanUp(self):
blowUp = True blowUp = True
ordering = [] ordering = []
@ -547,6 +563,50 @@ class TestModuleCleanUp(unittest.TestCase):
'tearDownModule', 'cleanup_good']) 'tearDownModule', 'cleanup_good'])
self.assertEqual(unittest.case._module_cleanups, []) self.assertEqual(unittest.case._module_cleanups, [])
def test_addClassCleanup_arg_errors(self):
cleanups = []
def cleanup(*args, **kwargs):
cleanups.append((args, kwargs))
class TestableTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.addClassCleanup(cleanup, 1, 2, function=3, cls=4)
with self.assertRaises(TypeError):
cls.addClassCleanup(function=cleanup, arg='hello')
def testNothing(self):
pass
with self.assertRaises(TypeError):
TestableTest.addClassCleanup()
with self.assertRaises(TypeError):
unittest.TestCase.addCleanup(cls=TestableTest(), function=cleanup)
runTests(TestableTest)
self.assertEqual(cleanups,
[((1, 2), {'function': 3, 'cls': 4})])
def test_addCleanup_arg_errors(self):
cleanups = []
def cleanup(*args, **kwargs):
cleanups.append((args, kwargs))
class TestableTest(unittest.TestCase):
def setUp(self2):
self2.addCleanup(cleanup, 1, 2, function=3, self=4)
with self.assertWarns(DeprecationWarning):
self2.addCleanup(function=cleanup, arg='hello')
def testNothing(self):
pass
with self.assertRaises(TypeError):
TestableTest().addCleanup()
with self.assertRaises(TypeError):
unittest.TestCase.addCleanup(self=TestableTest(), function=cleanup)
runTests(TestableTest)
self.assertEqual(cleanups,
[((), {'arg': 'hello'}),
((1, 2), {'function': 3, 'self': 4})])
def test_with_errors_in_addClassCleanup(self): def test_with_errors_in_addClassCleanup(self):
ordering = [] ordering = []

View File

@ -527,7 +527,33 @@ class finalize:
class _Info: class _Info:
__slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
def __init__(self, obj, func, *args, **kwargs): def __init__(*args, **kwargs):
if len(args) >= 3:
self, obj, func, *args = args
elif not args:
raise TypeError("descriptor '__init__' of 'finalize' object "
"needs an argument")
else:
if 'func' not in kwargs:
raise TypeError('finalize expected at least 2 positional '
'arguments, got %d' % (len(args)-1))
func = kwargs.pop('func')
if len(args) >= 2:
self, obj, *args = args
import warnings
warnings.warn("Passing 'func' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
if 'obj' not in kwargs:
raise TypeError('finalize expected at least 2 positional '
'arguments, got %d' % (len(args)-1))
obj = kwargs.pop('obj')
self, *args = args
import warnings
warnings.warn("Passing 'obj' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
args = tuple(args)
if not self._registered_with_atexit: if not self._registered_with_atexit:
# We may register the exit function more than once because # We may register the exit function more than once because
# of a thread race, but that is harmless # of a thread race, but that is harmless

View File

@ -0,0 +1,5 @@
Deprecated passing required arguments like *func* as keyword arguments
in functions which should accept arbitrary keyword arguments and pass them
to other function. Arbitrary keyword arguments (even with names "self" and
"func") can now be passed to these functions if the required arguments are
passed as positional arguments.