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.
(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
========================

View file

@ -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

View file

@ -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)

View file

@ -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
@ -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):

View file

@ -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)

View file

@ -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)

View file

@ -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
@ -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
@ -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
@ -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

View file

@ -60,7 +60,7 @@ 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'
@ -68,6 +68,17 @@ def wrapper(func, *args, **kwds):
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()

View file

@ -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))

View file

@ -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]
@ -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:
@ -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."

View file

@ -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:

View file

@ -49,6 +49,9 @@ def create_future(state=PENDING, exception=None, result=None):
def mul(x, y):
return x * y
def capture(*args, **kwargs):
return args, kwargs
def sleep_and_raise(t):
time.sleep(t)
raise Exception('this is an exception')
@ -658,6 +661,13 @@ def test_submit(self):
def test_submit_keyword(self):
future = self.executor.submit(mul, 2, y=8)
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):
self.assertEqual(

View file

@ -574,6 +574,7 @@ def test_callback(self):
((), dict(example=1)),
((1,), dict(example=1)),
((1,2), dict(example=1)),
((1,2), dict(self=3, callback=4)),
]
result = []
def _exit(*args, **kwds):
@ -596,6 +597,16 @@ def _exit(*args, **kwds):
self.assertIsNone(wrapper[1].__doc__, _exit.__doc__)
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):
exc_raised = ZeroDivisionError
def _expect_exc(exc_type, exc, exc_tb):

View file

@ -352,6 +352,16 @@ async def _exit(*args, **kwds):
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 def test_async_push(self):
exc_raised = ZeroDivisionError

View file

@ -464,6 +464,7 @@ class A(object):
positional = functools.partialmethod(capture, 1)
keywords = functools.partialmethod(capture, a=2)
both = functools.partialmethod(capture, 3, b=4)
spec_keywords = functools.partialmethod(capture, self=1, func=2)
nested = functools.partialmethod(positional, 5)
@ -497,6 +498,8 @@ def test_arg_combinations(self):
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):
self.assertEqual(self.a.nested(), ((self.a, 1, 5), {}))
self.assertEqual(self.a.nested(6), ((self.a, 1, 5, 6), {}))
@ -550,6 +553,14 @@ def test_invalid_args(self):
with self.assertRaises(TypeError):
class B(object):
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):
self.assertEqual(repr(vars(self.A)['both']),

View file

@ -70,6 +70,9 @@ def traced_func_calling_generator():
def traced_doubler(num):
return num * 2
def traced_capturer(*args, **kwargs):
return args, kwargs
def traced_caller_list_comprehension():
k = 10
mylist = [traced_doubler(i) for i in range(k)]
@ -270,6 +273,15 @@ def test_simple_caller(self):
}
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):
self.tracer.runfunc(traced_func_importing_caller, 1)

View file

@ -1839,6 +1839,35 @@ def add(x,y,z):
self.assertEqual(f.alive, False)
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):
a = self.A()
res = []

View file

@ -451,7 +451,22 @@ def runctx(self, cmd, globals=None, locals=None):
sys.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
if not self.donothing:
sys.settrace(self.globaltrace)

View file

@ -86,9 +86,21 @@ def _id(obj):
_module_cleanups = []
def addModuleCleanup(function, *args, **kwargs):
def addModuleCleanup(*args, **kwargs):
"""Same as addCleanup, except the cleanup items are called even if
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))
@ -463,18 +475,44 @@ def addTypeEqualityFunc(self, 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
completed. Functions added are called on a LIFO basis and are
called after tearDown on test failure or success.
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))
@classmethod
def addClassCleanup(cls, function, *args, **kwargs):
def addClassCleanup(*args, **kwargs):
"""Same as addCleanup, except the cleanup items are called even if
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))
def setUp(self):

View file

@ -403,6 +403,22 @@ class Module(object):
self.assertEqual(str(e.exception), 'CleanUpExc')
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):
blowUp = True
ordering = []
@ -547,6 +563,50 @@ def tearDownClass(cls):
'tearDownModule', 'cleanup_good'])
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):
ordering = []

View file

@ -527,7 +527,33 @@ class finalize:
class _Info:
__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:
# We may register the exit function more than once because
# 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.