Ваш диагноз неверен; фактически, functools.wraps
сохраняет сигнатуру функции с двойным оформлением:
>>> import inspect
>>> inspect.signature(foo)
<Signature (x=1, y=3)>
Мы также можем заметить, что это не проблема при вызове функции с неправильной сигнатурой, так как это вызовет TypeError
, а не KeyError
.
Похоже, у вас сложилось впечатление, что при использовании только одного декоратора kwargs
будет заполнено значениями аргумента по умолчанию. Этого не происходит вообще:
def test_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('args:', args)
print('kwargs:', kwargs)
return func(*args, **kwargs)
return wrapper
@test_decorator
def foo(x=1):
print('x:', x)
Вывод:
>>> foo()
args: ()
kwargs: {}
x: 1
Итак, как вы можете видеть, ни args
, ни kwargs
не получают значения по умолчанию для аргумента, даже когда используется только один декоратор. Они оба пусты, потому что foo()
вызывает функцию-оболочку без позиционных аргументов и без аргументов ключевых слов.
Фактическая проблема заключается в том, что в вашем коде есть логическая ошибка. Декораторы validate_x
и validate_y
ожидают, что аргументы будут переданы в качестве аргументов ключевого слова, но на самом деле они могут передаваться как позиционные аргументы или вообще не передаваться (поэтому применяются значения по умолчанию), в этом случае 'x'
и / или 'y'
не будет присутствовать в kwargs
.
Нет простого способа заставить ваших декораторов работать с аргументом, который может быть передан как ключевое или позиционное; если вы делаете аргументы только для ключевых слов, то вы можете проверить, находятся ли 'x'
или 'y'
в kwargs
, прежде чем их проверять.
def validate_x(func):
@wraps(func)
def wrapper(*args, **kwargs):
if 'x' in kwargs and kwargs['x'] > 2:
raise ValueError('Invalid x, should be <= 2, was ' + str(x))
return func(*args, **kwargs)
return wrapper
@validate_x
def bar(*, x=1): # keyword-only arg, prevent passing as positional arg
...
Обычно лучше явно raise
сообщить об ошибке, вместо использования assert
, потому что ваша программа может быть запущена с assert
отключено .
Остерегайтесь также, что возможно объявить функцию, такую как @validate_x def baz(*, x=5): ...
, где по умолчанию x
является недействительным. Это не вызовет никакой ошибки, поскольку значение аргумента по умолчанию не проверяется декоратором.