Reputation: 14086
I have an API method that accepts a callback. The callback expects one argument.
I would like this method to pass a second argument to callbacks that accept it. However, I must maintain compatibility with callbacks that accept only the original argument. (In fact, I expect that most users will not care about the additional argument, so it would be annoying to force them to explicitly ignore it.)
I know that this can be done using inspect
. I'm wondering if there is an "idiomatic" or commonly used solution that's not quite so heavyweight.
Upvotes: 5
Views: 1273
Reputation: 106455
A simpler solution would be to use a try
block to try calling the callback with a second argument first, before falling back to calling with just one argument in the except
block:
try:
callback(first, second)
except TypeError as e:
if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
raise
callback(first)
Upvotes: 2
Reputation: 558
I think you can use __code__ to look how much arguments needed by the callback.
if callback.__code__.co_argcount == 2:
callback(arg1, arg2)
else:
callback(arg1)
This code isn't tested but it should work.
Upvotes: 5
Reputation: 14086
Using a function wrapper:
from inspect import signature, Parameter
def ignore_extra_arguments(function):
positional_count = 0
var_positional = False
keyword_names = set()
var_keyword = False
for p in signature(function).parameters.values():
if p.kind == Parameter.POSITIONAL_ONLY:
positional_count += 1
elif p.kind == Parameter.POSITIONAL_OR_KEYWORD:
positional_count += 1
keyword_names.add(p.name)
elif p.kind == Parameter.VAR_POSITIONAL:
var_positional = True
elif p.kind == Parameter.KEYWORD_ONLY:
keyword_names.add(p.name)
elif p.kind == Parameter.VAR_KEYWORD:
var_keyword = True
if var_positional:
new_args = lambda args: args
else:
new_args = lambda args: args[:positional_count]
if var_keyword:
new_kwargs = lambda kwargs: kwargs
else:
new_kwargs = lambda kwargs: {
name: value for name, value in kwargs.items()
if name in keyword_names
}
def wrapped(*args, **kwargs):
return function(
*new_args(args),
**new_kwargs(kwargs)
)
return wrapped
It works, but it's a bit brute-force.
A simpler version, assuming that function
has no keyword or variadic parameters:
from inspect import signature
def ignore_simple(function):
count = len(signature(function).parameters)
return lambda *args: function(*args[:count])
Upvotes: -1