Курс по Python: https://stepik.org/course/100707
На предыдущем занятии, мы познакомились с декораторами и рассмотрели некоторые примеры их
работы. Здесь немного углубимся в эту тему и начнем с декораторов, которым
можно дополнительно передавать аргументы.
Предположим, что
мы создаем декоратор для вычисления производной функции. Используя уже
имеющиеся знания, это можно сделать, следующим образом:
def func_decorator(func):
def wrapper(x, *args, **kwargs):
dx = 0.0001
res = (func(x + dx, *args, **kwargs) - func(x, *args, **kwargs)) / dx
return res
return wrapper
И к функции,
например, sin(x) применить этот
декоратор (не забываем import math):
@func_decorator
def sin_df(x):
return math.sin(x)
df = sin_df(math.pi/3)
print(df)
После запуска
программы видим искомый результат. Но, что если, мы хотим управлять значением dx, передавая его
как аргумент декоратору? Прописывать еще один параметр непосредственно у
func_decorator, не лучший вариант:
def func_decorator(func, dx=0.0001):
В этом случае, у
нас перестанет работать синтаксис с собачкой:
@func_decorator(dx=0.01)
def sin_df(x):
return math.sin(x)
и попытка
выполнить такую программу, приведет к ошибке. Для корректной реализации такой
задачи следует обернуть имеющийся у нас декоратор в еще одну внешнюю функцию:
def df_decorator(dx=0.0001):
def func_decorator(func):
def wrapper(x, *args, **kwargs):
res = (func(x+dx, *args, **kwargs) - func(x, *args, **kwargs)) / dx
return res
return wrapper
return func_decorator
И применить уже
ее к функции синуса:
@df_decorator(dx=0.01)
def sin_df(x):
return math.sin(x)
В этом случае,
аргумент будет передан в первую функцию, ссылка sin_df – во вторую
вложенную функцию, а затем, через ссылку на третью функцию, мы можем вычислять
значение производной:
Все это
эквивалентно следующим вызовам:
f = df_decorator(dx=0.01)
sin_df = f(sin_df)
Или, в более
краткой форме:
sin_df = df_decorator(dx=0.01)(sin_df)
Но, запись через
символ «собачки» куда нагляднее и проще, поэтому он, как правило, используется
на практике.
Контроль имени и описания декорируемой функции
Во второй части
занятия я хочу коснуться проблемы потери имени и описания декорируемой функции.
О чем здесь речь? Вот смотрите, имя нашей декорированной функции можно узнать
по специальному свойству __name__:
Сейчас оно принимает
значение wrapper, что не
удивительно, так как это и есть ссылка на эту функцию. Но изначально имя было
другим (декоратор в комментарий и повторяем) – sin_df. Иногда важно
сохранять исходные имена и при декорировании. Как это сделать? В самом простом
варианте мы можем вручную внутри функции func_decorator изменить имя,
оставив исходное:
def df_decorator(dx=0.0001):
def func_decorator(func):
def wrapper(x, *args, **kwargs):
res = (func(x+dx, *args, **kwargs) - func(x, *args, **kwargs)) / dx
return res
wrapper.__name__ = func.__name__
return wrapper
return func_decorator
И, как видим, это работает. И тот же самый
фокус можем проделать с описанием функции:
@df_decorator(dx=0.01)
def sin_df(x):
"""Функция для вычисления производной синуса"""
return math.sin(x)
Это описание
доступно с помощью специального свойства __doc__:
Для
декорированной функции оно равно None, а для исходной – заданная строка. Также
сохраняем описание, прописав в функции func_decorator строчку:
wrapper.__doc__ = func.__doc__
Все, теперь у
нас сохраняются и имена и описания декорируемых функций.
Но есть более
простой и продвинутый способ сохранять свойства __name__ и __doc__ - с помощью
специального декоратора:
from functools import wraps
Если мы
декорируем вложенную функцию wrapper:
то строчки можно
убрать (я поставлю в комментарии):
# wrapper.__name__ = func.__name__
# wrapper.__doc__ = func.__doc__
И, выполняя программу,
видим, что имя функции и ее описание остаются прежними. То есть, декоратор wraps автоматически
сохраняет эти свойства.
На этом мы
завершим тему декораторов и функций языка Python. Этого
материала, вам вполне хватит для программирования большинства задач и, кроме
того, позволит понимать уже существующие программы. До встречи на следующем
уроке!
Курс по Python: https://stepik.org/course/100707