По определению, декоратор - это функция, которая принимает другую функцию и расширяет поведение последней, не изменяя ее явно. На самом деле это не так. Декораторы предоставляют простой синтаксис для вызова функций высшего порядка.
Прежде чем понять как работают декораторы и начать создавать их, вспомним, как работают функции:
Теперь владея этими знаниями, применим их, что бы создать простой декоратор:
def sample_decorator(func): def wrapper(): print('Я родился...') func() print('Меня зовут Лунтик!') return wrapper def say(): print('Привет Мир.') # Декорирование say = sample_decorator(say) # Выполняем функцию say() # Я родился... # Привет Мир. # Меня зовут Лунтик!
Декорирование происходит в следующей строке:
say = sample_decorator(say)
Здесь имя say
указывает на внутреннюю функцию wrapper()
. Важно понимать то, что при вызове функции sample_decorator(say)
с переданной в качестве аргумента функцией say()
, возвращается вложенная функция wrapper()
в качестве результата.
>>> say # <function sample_decorator.<locals>.wrapper at 0x7f591a0a42f0>
В свою очередь функция wrapper()
имеет ссылку на переданную в качестве аргумента функцию say()
и вызывает эту функцию между двумя вызовами встроенной функции print()
.
Проще говоря: декораторы обертывают функцию, изменяя ее поведение.
Так как wrapper()
является обычной функцией Python, следовательно декоратор может изменять переданную функцию динамически.
Декораторы теперь можно хранить в словаре, списке или другом контейнере. Обращение осуществляется согласно синтаксису контейнера:
# декораторы хранятся в словаре decorators = {'dec': dec} @decorators['dec'] def foo(x): print('Hello') # или списке decorators = [dec1, dec2, dec3] @decorators[0] def foo(x): print('Hello')
В ранних версиях Python на такую конструкцию поднимется исключение SyntaxError
.
В этом примере в разное время будет выводится разные приветствия.
from datetime import datetime def say(): print('Привет Мир.') def time_talk(func): def wrapper(): if 0 <= datetime.now().hour < 9: talk1 = 'Я еще не родился' talk2 = '' elif 9 <= datetime.now().hour < 13: talk1 = 'Я родился...' talk2 = 'Меня зовут Лунтик!' elif 13 <= datetime.now().hour < 24: talk1 = 'Меня зовут Лунтик.' talk2 = 'Поиграй со мной.' print(talk1) if talk2: func() print(talk2) return wrapper say = time_talk(say) say() # С 13 до 24 часов будет выведено # Меня зовут Лунтик. # Привет Мир. # Поиграй со мной.
Способ, который декорирует функцию say()
- многословен, приходится набирать имя say
три раза. Кроме того, декорирование скрывается под определением функции. Вместо этого Python позволяет использовать декораторы более простым способом с символом "собака" @
.
Следующий синтаксис декорирования @time_talk
функции talk()
будет делать с последней то же самое, что и в предыдущем примере с функцией say()
:
@time_talk def talk(): print('Как здесь интересно!') talk() # С 9 до 13 часов будет выведено # Я родился... # Как здесь интересно! # Меня зовут Лунтик.
Декоратор - это всего лишь обычная функция Python. Как мы знаем, что модули в основном состоят из функций и классов. Давайте переместим декоратор time_talk
в его собственный модуль, который может быть использован во многих других функциях. Для этого создадим файл с именем decorators.py
и скопируем в него код строки импорта from datetime import datetime
и код определения функции time_talk()
. Теперь декоратор @time_talk
можно использовать следующим образом:
from decorators import time_talk @time_talk def talk(): print('Как здорово!') talk() # С 0 до 9 часов будет выведено # Я еще не родился