Вот новый способ решения этой проблемы с использованием inspect.signature
(для Python 3.3+). Я приведу пример, который можно сначала запустить / протестировать самостоятельно, а затем покажу, как с его помощью изменить исходный код.
Вот тестовая функция, которая просто суммирует любые аргументы / kwargs, данные ей; требуется по крайней мере один аргумент (a
), и есть один аргумент только для ключевого слова со значением по умолчанию (b
), просто для проверки различных аспектов сигнатур функций.
def silly_sum(a, *args, b=1, **kwargs):
return a + b + sum(args) + sum(kwargs.values())
Теперь давайте создадим оболочку для silly_sum
, которую можно вызывать так же, как silly_sum
(с исключением, к которому мы вернемся), но который передается только в kwargs для обернутого silly_sum
.
def wrapper(f):
sig = inspect.signature(f)
def wrapped(*args, **kwargs):
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
print(bound_args) # just for testing
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args")) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
return f(**all_kwargs)
return wrapped
sig.bind
возвращает объект BoundArguments
, но это не учитывает значения по умолчанию, если вы не вызовете apply_defaults
явно. Это также приведет к созданию пустого кортежа для аргументов и пустого слова для kwargs, если не было указано *args
/ **kwargs
.
sum_wrapped = wrapper(silly_sum)
sum_wrapped(1, c=9, d=11)
# prints <BoundArguments (a=1, args=(), b=1, kwargs={'c': 9, 'd': 11})>
# returns 22
Затем мы просто получаем словарь аргументов и добавляем **kwargs
in. Исключением из использования этой оболочки является то, что *args
не может быть передано в функцию. Это потому, что для них нет названий, поэтому мы не можем конвертировать их в kwargs. Если приемлемо передавать их в виде kwarg с именем args, это можно сделать вместо этого.
Вот как это можно применить к исходному коду:
import inspect
class mydec(object):
def __init__(self, f, *args, **kwargs):
self.f = f
self._f_sig = inspect.signature(f)
def __call__(self, *args, **kwargs):
bound_args = self._f_sig.bind(*args, **kwargs)
bound_args.apply_defaults()
all_kwargs = bound_args.arguments
assert len(all_kwargs.pop("args")) == 0
all_kwargs.update(all_kwargs.pop("kwargs"))
hozer(**all_kwargs)
self.f(*args, **kwargs)