Как подружиться с декораторами 

Как подружиться с декораторами 

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!

О декораторах можно рассказывать долго, возможности их применения безграничны — это логирование, применение различных метрик, авторизация, отладка и многое другое. Надеемся, нам удалось пробудить ваш интерес к более глубокому изучению декораторов и, конечно, применению их на практике! 



Report Page