Передавать дополнительные необязательные аргументы обратным вызовам, не нарушая существующие обратные вызовы
У меня есть метод API, который принимает обратный вызов. Обратный вызов ожидает один аргумент.
Я хотел бы, чтобы этот метод передавал второй аргумент обратным вызовам, которые принимают его. Однако я должен поддерживать совместимость с обратными вызовами, которые принимают только исходный аргумент. (На самом деле, я ожидаю, что большинство пользователей не будут заботиться о дополнительном аргументе, поэтому было бы неприятно заставлять их явно игнорировать его.)
Я знаю, что это можно сделать с помощью inspect
, Мне интересно, есть ли "идиоматическое" или обычно используемое решение, которое не так уж тяжеловесно.
3 ответа
Я думаю, что вы можете использовать __code__, чтобы посмотреть, сколько аргументов необходимо для обратного вызова.
if callback.__code__.co_argcount == 2:
callback(arg1, arg2)
else:
callback(arg1)
Этот код не проверен, но он должен работать.
Более простым решением было бы использовать try
блок, чтобы попытаться вызвать обратный вызов со вторым аргументом, прежде чем вернуться к вызову только с одним аргументом в except
блок:
try:
callback(first, second)
except TypeError as e:
if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
raise
callback(first)
Использование функции-обёртки:
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
Это работает, но это немного грубо.
Более простая версия, при условии, что function
не имеет ключевых слов или переменных параметров:
from inspect import signature
def ignore_simple(function):
count = len(signature(function).parameters)
return lambda *args: function(*args[:count])