Как подружиться с декораторами
Karpov CoursesДекораторы — мощный инструмент, который позволяет эффективно переиспользовать код в рамках концепции DRY(Don't Repeat Yourself), меняя поведение функции без необходимости изменения её самой.
Допустим, у нас есть некая функция и, как у любой функции, у нее есть вход, тело и выход. Декоратор — это обертка над функцией, которая может менять её поведение, будь то вход, выход или даже происходящее внутри неё.
Таким образом, декораторы в Python позволяют расширять поведение функций без изменения их исходного кода. По сути, декоратор — это функция, которая принимает другую функцию в качестве аргумента и возвращает новую функцию.
На самом деле декораторы могут принимать на вход не только функции, но и любые callable объекты (те, что можно вызвать). Также они могут возвращать что угодно, например, объекты классов.
Это возможно благодаря тому, что в Python всё является объектами, в том числе и функции, а значит, как и любой другой объект, их можно использовать в качестве аргументов.
Рассмотрим для примера такую функцию:
>>>def greet(name):
>>> return f"Привет, {name}!"
Мы можем вызвать её, передав имя в качестве аргумента:
>>> print(greet('Валерия'))
Привет, Валерия!
Присвоить результат выполнения функции переменной:
>>> greeting = greet('Павел')
>>> print(greeting)
Привет, Павел!
А можем создать новую функцию и передать ей greet в качестве аргумента:
>>> def greet_people(func):
>>> return func('Мария')
>>> print(greet_people(greet))
Привет, Мария!
Функции можно определять, вызывать и возвращать внутри другой функции:
>>>def outer_f():
>>> def inner_f():
>>> print("Это внутренняя функция")
>>>
>>> inner_f()
>>>
>>> def inner_2f():
>>> return "Это ещё одна внутренняя функция"
>>>
>>> print("Это внешняя функция")
>>>
>>> return inner_2f()
>>>result = outer_f()
>>>print(result)
Это внутренняя функция
Это внешняя функция
Это ещё одна внутренняя функция
Мы можем взять за основу эту структуру и создать простейший декоратор.
Сначала напишем функцию-декоратор, которой будем передавать в качестве аргумента (func) функции для изменения. Затем уже внутри декоратора создадим ещё одну функцию, которая будет оборачивать передаваемые функции и модифицировать их. При этом сама функция-декоратор будет возвращать функцию-обёртку:
>>>def change_hi(func): # это наш декоратор, который принимает функции для изменения
>>> def wrapper(): # это функция-обёртка, которая модифицирует поведение функции, которую мы передаём вместо аргумента func
>>> func() # вызываем func
>>> print('Как здорово, что вы пришли!') # добавляем к вызову дополнительное поведение
>>> print('Не желаете чашечку чая?') # ещё что-нибудь пишем
>>> return wrapper # возвращаем измененный результат
А это наша функция func, поведение которой мы будем менять:
>>>def hi():
>>> print('Доброе утро!')
Проверим её работу:
>>>hi() Доброе утро!
Теперь применим наш декоратор к этой функции:
>>>new_greet = change_hi(hi) >>>print(new_greet()) Доброе утро! Как здорово, что вы пришли! Не желаете чашечку чая?
Кстати, применить декоратор можно и более популярным способом, что будет равносильно по смыслу предыдущей записи:
>>>@change_hi
>>>def hi():
>>> print('Доброе утро!')
>>>
>>>hi()
Доброе утро!
Как здорово, что вы пришли!
Не желаете чашечку чая?
Аргументы декоратора и аргументы функции
Передать аргументы функции можно как явно — в виде конкретных переменных, так и в виде магических переменных *args и **kwargs. Это позволит применять декораторы к любой функции, с любым числом входных аргументов.
Перейдём к более практичным примерам и напишем полезный декоратор, который будет выводить время выполнения любой функции:
>>>import time
>>>def timer(func):
>>> def wrapper(*args,**kwargs):
>>> time_start = time.time()
>>> result = func(*args,**kwargs)
>>> t_time = (time.time() - time_start)
>>> print(f'Время выполнения функции {func.__name__} = {t_time} секунд!')
>>> print(f'Результат = {result}')
>>> return result
>>> return wrapper
Обратите внимание: декоратор мы указываем только в момент определения функции:
>>>@timer >>>def some_equation(a,b): >>> answer = 3/a**b - b*a/b**a >>> return answer
После чего мы используем функцию как обычно:
>>>some_equation(10,3)
Получаем вывод:
Время выполнения функции some_equation = 8.344650268554688e-06 секунд! Результат = 0.0024919473657470915
Помимо аргументов, передаваемых в функцию, может быть полезно передавать аргументы и в сам декоратор. Рассмотрим еще один пример практичного декоратора, замедляющего время работы функции:
>>>import time
>>>def pause(sec):
>>> def decorator(func):
>>> def wrapper(*args, **kwargs):
>>> for i in range(sec,0,-1):
>>> print(f 'Waiting for {i} seconds...')
>>> time.sleep(1)
>>> return func(*args, **kwargs)
>>> return wrapper
>>> return decorator
>>>@pause(sec=3)
>>>def lazy_func(text):
>>> print(text)
Вызовем функцию:
>>>lazy_func("I'm so tired…")
Вывод:
Waiting for 3 seconds... Waiting for 2 seconds... Waiting for 1 seconds... I'm so tired…
Напоследок давайте внесём немного интерактива и напишем дружелюбный декоратор, который будет не только считать время выполнения функций, но и обрабатывать ошибки так, чтобы не оставлять нас один на один с полотном непонятного текста, и вообще будет всячески нас приободрять:
>>>import time
>>>def func_buddy(func):
>>> def wrapper(*args,**kwargs):
>>> print('Привет! Я буду контролировать выполнение твоей функции и докладывать о
>>> процессе!')
>>> time.sleep(1)
>>> print('Запускаю функцию...')
>>> time.sleep(1)
>>> time_start = time.time()
>>> try:
>>> result = func(*args,**kwargs)
>>> t_time = (time.time() - time_start)
>>> print(f'Функция {func.__name__} отработала успешно! Вот результат: {result}. Время >>> выполнения {t_time} секунд!')
>>> except Exception as err:
>>> result = err
>>> print(f'Шеф,у нас проблемы: {err}')
>>> time.sleep(1)
>>> print('Не переживай, в следующий раз ты обязательно справишься!')
>>> time.sleep(1)
>>> return result
>>> return func(*args,**kwargs)
>>> return wrapper
Напишем функцию и обернем в декоратор:
>>>@func_buddy >>>def my_func(x,y): >>> return x**y%y
Попробуйте скопировать данный код и вызвать функцию my_func(x,y) с различными параметрами или напишите свою функцию и передайте в func_buddy!
О декораторах можно рассказывать долго, возможности их применения безграничны — это логирование, применение различных метрик, авторизация, отладка и многое другое. Надеемся, нам удалось пробудить ваш интерес к более глубокому изучению декораторов и, конечно, применению их на практике!
