На этом шаге мы рассмотрим особенности определения и исполнения такого декоратора.
Вы хотели бы написать один декоратор, который можно было бы использовать и без аргументов - @decorator, и с необязательными аргументами - @decorator(x, y, z). Однако вы не видите простого пути сделать это из-за различий в условиях вызова простых декораторов и декораторов, принимающих аргументы.
Вот вариант логирующего кода, показанного на 160 шаге, который определяет такой декоратор:
>>> from functools import wraps, partial >>> import logging >>> def logged(func=None, *, level=logging.DEBUG, name=None, message=None): if func is SPAN CLASS="sl_sl">None: return partial(logged, level=level, name=name, message=message) logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): log.log(level, logmsg) return func(*args, **kwargs) return wrapper >>> # Пример использования >>> @logged(logging.DEBUG) def add(x, y): return x + y >>> @logged(logging.CRITICAL, 'example') def spam(): print('Spam!') >>>
Как вы можете видеть в этом примере, декоратор может быть использован как в простой форме (@logged), так и с необязательными аргументами (@logged(level=logging. CRITICAL, name='example')).
Задача, которую решает этот рецепт, на самом деле относится к единообразию, консистентности в программировании. При использовании декораторов большинство программистов обычно применяет их либо без аргументов, либо с аргументами, как показано в примере. С технической точки зрения декоратор с необязательными аргументами может быть использован так:
@logged() def add(x, y): return x + y
Однако это не особенно распространено и может привести к ошибкам, если программисты будут забывать добавлять дополнительные скобки. А этот рецепт просто заставляет декоратор одинаково работать и с дополнительными скобками, и без.
Чтобы понять принцип работы кода, вы должны четко понимать то, как декораторы применяются к функциям, а также условия их вызова. Для простого декоратора, такого как этот:
# Пример использования @logged def add(x, y): return x + y
def add(x, y): return x + y add = logged(add)
В этом случае обертываемая функция просто передается в logged первым аргументом. Поэтому в решении первый аргумент logged() - это обертываемая функция. Все остальные аргументы должны иметь значения по умолчанию.
Для декоратора, принимающего аргументы, такого как этот:
>>> @logged(logging.CRITICAL, 'example') def spam(): print('Spam!')
def spam(): print('Spam!') spam = logged(level=logging.CRITICAL, name='example')(spam)
При первичном вызове logged() обертываемая функция не передается. Так что в декораторе она должна быть необязательной. Это, в свою очередь, заставляет другие аргументы быть именованными. Более того, когда аргументы переданы, декоратор должен вернуть функцию, которая принимает функцию и оборачивает ее (см. 160 шаг). Чтобы сделать это, в решении используется хитрый трюк с functools.partial. Если точнее, он просто возвращает частично примененную версию себя, где все аргументы зафиксированы, за исключением обертываемой функции. См. 124 шаг, чтобы узнать больше об использовании partial().
На следующем шаге мы рассмотрим принудительная проверка типов в функции с использованием декоратора.