На этом шаге мы рассмотрим особенности решения этой задачи.
Вы хотите написать декоратор, который добавляет дополнительный аргумент в сигнатуру вызова обертываемой функции. Однако добавленный аргумент не может
перекрываться с существующими условиями вызова функции.
Дополнительные аргументы могут быть внедрены в сигнатуру вызова путем использования обязательных именованных аргументов. Рассмотрите такой декоратор:
>>> from functools import wraps >>> def optional_debug(func): @wraps(func) def wrapper(*args, debug=False, **kwargs): if debug: print('Calling', func.__name__) return func(*args, **kwargs) return wrapper >>> >>> @optional_debug def spam(a, b, c): print(a, b, c) >>> spam(1, 2, 3) 1 2 3 >>> spam(1, 2, 3, debug=True) Calling spam 1 2 3 >>>
Добавление аргументов в сигнатуру обернутой функции - не самое распространенное применение декораторов. Однако это может оказаться полезным приемом для избежания некоторых шаблонов повторения кода. Например, если у вас есть такой код:
def a(x, debug=False): if debug: print('Calling a') . . . def b(x, y, z, debug=False): if debug: print('Calling b') . . . def c(x, y, debug=False): if debug: print('Calling c') . . .
@optional_debug def a(x): . . . @optional_debug def b(x, y, z): . . . @optional_debug def c(x, y): . . .
Реализация этого рецепта базируется на том факте, что обязательные именованные аргументы легко добавить в функции, которые также принимают параметры *args и **kwargs. При использовании обязательного именованного аргумента он выделяется в специальный случай и удаляется из последующих вызовов, которые используют только оставшиеся позиционные и именованные аргументы.
Здесь есть хитрость, которая касается потенциального конфликта имен между добавленным аргументом и аргументами оборачиваемой функции. Например, если декоратор @optional_debug был применен к функции, которая уже имела аргумент debug, тогда все сломается. Если это вызывает опасения, то можно добавить дополнительную проверку:
>>> from functools import wraps >>> import inspect >>> def optional_debug(func): if 'debug' in inspect.getargspec(func).args: raise TypeError('debug argument already defined') @wraps(func) def wrapper(*args, debug=False, **kwargs): if debug: print('Calling', func.__name__) return func(*args, **kwargs) return wrapper >>>
Последнее уточнение касается правильного управления сигнатурами функций. Проницательный программист поймет, что сигнатура обернутых функций будет неправильной. Например:
>>> @optional_debug def add(x,y): return x+y >>> import inspect >>> print(inspect.signature(add)) (x, y) >>>
Это может быть исправлено с помощью такой модификации:
>>> from functools import wraps >>> import inspect >>> def optional_debug(func): if 'debug' in inspect.getargspec(func).args: raise TypeError('debug argument already defined') @wraps(func) def wrapper(*args, debug=False, **kwargs): if debug: print('Calling', func.__name__) return func(*args, **kwargs) sig = inspect.signature(func) parms = list(sig.parameters.values()) parms.append(inspect.Parameter('debug', inspect.Parameter.KEYWORD_ONLY, default=False)) wrapper.__signature__ = sig.replace(parameters=parms) return wrapper >>> >>> @optional_debug def add(x,y): return x+y >>> print(inspect.signature(add)) (x, y, *, debug=False) >>> add(2, 3) 5 >>>
На следующем шаге мы рассмотрим использование декораторов для исправления определений классов.