Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
Мы с вами уже
знаем, что любая функция в Python имеет два важных
атрибута:
- __name__ - название
(имя) функции;
- __doc__ - описание
функции (если оно присутствует).
Например, если
объявить функцию tr_per следующим образом:
def tr_per(a, b, c, add=0, mul=1):
"Вычисление периметра фигуры со сторонами a, b, c"
print(a, b, c, add, mul)
return (a + b + c) * mul + add
С выводом
атрибутов __name__ и __doc__ в консоль:
print(tr_per.__name__)
print(tr_per.__doc__)
то увидим
строчки:
tr_per
Вычисление
периметра фигуры со сторонами a, b, c
Однако, после
декорирования этой функции, например, с помощью partial:
rect_per = functools.partial(tr_per, 0, mul=2)
Эти атрибуты
меняют свои значения:
print(rect_per.__doc__)
print(rect_per.__name__) # AttributeError
При этом часто
требуется, чтобы значения этих атрибутов соответствовали изначальным, как у
декорируемой функции (в нашем примере, tr_per). Для этого
можно воспользоваться функцией update_wrapper() модуля functools, которая
копирует информацию атрибутов __name__ и __doc__ из одной
функции в другую. В нашем случае ее следует вызвать следующим образом:
functools.update_wrapper(rect_per, tr_per)
Теперь атрибуты rect_per.__doc__ и rect_per.__name__ будут такими
же, как и у функции tr_per.
Декоратор functools.wraps
На одном из
занятий по базе языка Python мы с вами
рассматривали создание своих собственных декораторов функций. В них мы также
сохраняли метаданные __name__ и __doc__, но делали
это с помощью декоратора functools.wraps. Для
целостности материала, давайте еще раз посмотрим, как это делается.
Сформируем
декоратор avr_decor для функции tr_per(), который
позволит вычислять среднее арифметическое трех переданных чисел a, b, c:
def avr_decor(func):
def wrapper(*args, **kwargs):
result = func(*args, add=0, mul=1/3, **kwargs)
return result
return wrapper
И сразу применим
его к функции tr_per():
@avr_decor
def tr_per(a, b, c, add=0, mul=1):
"Вычисление периметра фигуры со сторонами a, b, c"
print(a, b, c, add, mul)
return (a + b + c) * mul + add
Для примера
вычислим среднее арифметическое и выведем результат с метаданными
декорированной функции:
res = tr_per(1, 2, 3)
print(res) # 2.0
print(tr_per.__name__) # wrapper
print(tr_per.__doc__) # None
Как видите,
теперь функция вычисляет среднее арифметическое, но атрибуты __name__ и __doc__ не
сохранились. Поправить это можно несколькими способами. Либо воспользоваться
уже знакомой нам функцией update_wrapper() следующим
образом:
def avr_decor(func):
def wrapper(*args, **kwargs):
result = func(*args, add=0, mul=1/3, **kwargs)
return result
functools.update_wrapper(wrapper, func)
return wrapper
Либо применить
декоратор wraps к вложенной
функции wrapper:
def avr_decor(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, add=0, mul=1/3, **kwargs)
return result
return wrapper
Результат будет тем
же самым.
Декоратор с помощью класса
Аналогичный
декоратор можно реализовать и с помощью класса. Например:
class AvrDecor:
def __init__(self, func):
self.func = func
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
return self.func(*args, add=0, mul=1/3, **kwargs)
Когда
применяется декоратор AvrDecor:
@AvrDecor
def tr_per(a, b, c, add=0, mul=1):
"Вычисление периметра фигуры со сторонами a, b, c"
print(a, b, c, add, mul)
return (a + b + c) * mul + add
то автоматически
создается объект этого класса и вызывается инициализатор с передачей в него
ссылки на декорируемую функцию tr_per. Ссылка на
функцию сохраняется в локальном атрибуте self.func, а в самом
объекте self создаются
атрибуты __name__ и __doc__ с помощью
функции update_wrapper().
После
декорирования переменная tr_per будет ссылаться уже
на объект класса AvrDecor, а не на объект-функцию. При этом
операция вызова:
передаст
управление магическому методу __call__() класса AvrDecor. Этот метод
вызовет функцию self.func, указанную при декорировании, то есть,
исходную функцию tr_per(), и возвратит результат ее работы.
При выводе
результатов декорированной функции и ее мета-данных:
print(res) # 2.0
print(tr_per.__name__) # tr_per
print(tr_per.__doc__) # Вычисление периметра фигуры со сторонами a, b, c
увидим ожидаемый
результат.
Таким образом,
оба инструмента - update_wrapper и @wraps – помогают сохранить оригинальные
метаданные функции при её декорировании. Поэтому если важно поддерживать исходную
информацию о функциях рекомендуется использовать именно эти методы, особенно
при написании сложных декораторов, где важна читаемость кода и совместимость с
инструментами разработки интегрированной среды и генераторов документации.
Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs