Декораторы — важная часть Python. Простыми словами: это функции, которые изменяют функциональность других функций. Они помогают делать наш код короче и более питоничным. Большинство новичков не знают, где их использовать, поэтому я расскажу о некоторых областях, где декораторы могут сделать ваш код чище. Сначала давайте обсудим, как писать собственные декораторы.

Это, вероятно, одна из самых сложных концепций для освоения. Мы будем обсуждать её шаг за шагом, чтобы вы могли полностью её понять.

Всё является объектом

Сначала давайте разберёмся с функциями в Python:

def hi(name="yasoob"): return "hi " + name print(hi()) # вывод: 'hi yasoob' # Мы даже можем присвоить функцию переменной, например greet = hi # Здесь мы не используем скобки, потому что мы не вызываем функцию hi # а вместо этого помещаем её в переменную greet. Давайте попробуем это запустить print(greet()) # вывод: 'hi yasoob' # Если мы удалим старую функцию hi, посмотрите, что произойдёт! del hi print(hi()) #выводит: NameError print(greet()) #вывод: 'hi yasoob'

Определение функций внутри функций

Это были основы функций. Давайте углубим ваши знания. В Python мы можем определять одну функцию внутри другой функции:

def hi(name="yasoob"): print("теперь вы внутри функции hi()") def greet(): return "теперь вы в функции greet()" def welcome(): return "теперь вы в функции welcome()" print(greet()) print(welcome()) print("теперь вы снова в функции hi()") hi() #вывод:теперь вы внутри функции hi() # теперь вы в функции greet() # теперь вы в функции welcome() # теперь вы снова в функции hi() # Приведённое выше показывает, что при каждом вызове hi() функции greet() и welcome() будут вызваны одновременно. # Таким образом, функции greet() и welcome() недоступны вне функции hi(), например: greet() #вывод: NameError: name 'greet' is not defined

Теперь мы знаем, что можно определять другие функции внутри функции. Иными словами, мы можем создавать вложенные функции. Теперь вам нужно узнать ещё немного: функции также могут возвращать функции.

Возврат функций из функций

На самом деле, нет необходимости выполнять другую функцию внутри функции; мы также можем вернуть её как результат:

def hi(name="yasoob"): def greet(): return "теперь вы в функции greet()" def welcome(): return "теперь вы в функции welcome()" if name == "yasoob": return greet else: return welcome a = hi() print(a) #выводит: <function greet at 0x7f2143c01500> # Приведённое выше ясно показывает, что `a` теперь указывает на функцию greet() внутри функции hi() # Теперь попробуйте это print(a()) #выводит: теперь вы в функции greet()

Посмотрите на этот код ещё раз. В операторе if/else мы возвращаем greet и welcome, а не greet() и welcome(). Почему так? Потому что когда вы ставите пару круглых скобок после неё, функция выполняется; однако если вы не ставите скобки после неё, её можно передавать и присваивать другим переменным, не выполняя. Понимаете? Позвольте объяснить немного подробнее.

  • Когда мы пишемa = hi(),hi()она выполняется, и поскольку параметр name по умолчанию равен yasoob, функцияgreet()возвращается.

  • Мы также можем вывестиhi()(), что выведет:теперь вы в функции greet()。

  • Если мы изменим инструкцию наa = hi(name = "ali"), тоwelcome()функция будет возвращена.

Передача функции в качестве аргумента другой функции

def hi(): return "привет yasoob!" def doSomethingBeforeHi(func): print("Я выполняю некоторую скучную работу перед выполнением hi()") print(func()) doSomethingBeforeHi(hi) #вывод:Я выполняю некоторую скучную работу перед выполнением hi() # привет yasoob!

Теперь у вас есть все необходимые знания, чтобы узнать, что такое декораторы на самом деле. Декораторы позволяют выполнять код до и после функции.

Ваш первый декоратор

В предыдущем примере мы фактически уже создали декоратор! Теперь давайте изменим предыдущий декоратор и напишем немного более полезную программу:

def a_new_decorator(a_func): def wrapTheFunction(): print("Я выполняю некоторую скучную работу перед выполнением a_func()") a_func() print("Я выполняю некоторую скучную работу после выполнения a_func()") return wrapTheFunction def a_function_requiring_decoration(): print("Я — функция, которой нужна некоторая декорация, чтобы убрать мой дурной запах") a_function_requiring_decoration() #выводит: "Я — функция, которой нужна некоторая декорация, чтобы убрать мой дурной запах" a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) #теперь a_function_requiring_decoration обёрнута с помощью wrapTheFunction() a_function_requiring_decoration() #выводит:Я выполняю некоторую скучную работу перед выполнением a_func() # Я — функция, которой нужна некоторая декорация, чтобы убрать мой дурной запах # Я выполняю некоторую скучную работу после выполнения a_func()

Видите? Мы только что применили принципы, которые изучили ранее. Именно это и делают декораторы в Python! Они оборачивают функцию и так или иначе изменяют её поведение. Теперь вы можете задаться вопросом: в нашем коде мы не использовали символ@? Это просто сокращённый способ создать декорированную функцию. Вот как мы используем@для запуска предыдущего кода:

@a_new_decorator def a_function_requiring_decoration(): """Эй, ты! Декорируй меня!""" print("Я — функция, которой нужна некоторая декорация, чтобы " "убрать мой дурной запах") a_function_requiring_decoration() #выводит: Я выполняю некоторую скучную работу перед выполнением a_func() # Я — функция, которой нужна некоторая декорация, чтобы убрать мой дурной запах # Я выполняю некоторую скучную работу после выполнения a_func() #@a_new_decorator — это просто краткая форма записи, означающая: a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

Надеюсь, теперь у вас есть базовое понимание того, как работают декораторы Python. Есть проблема, если мы запустим следующий код:

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

Это не то, что мы хотели! Вывод должен быть "a_function_requiring_decoration". Здесь функция была заменена на warpTheFunction. Она перезаписала имя нашей функции и строку документации. К счастью, Python предоставляет нам простую функцию для решения этой проблемы, а именно functools.wraps. Давайте изменим предыдущий пример, чтобы использовать functools.wraps:

from functools import wraps def a_new_decorator(a_func): @wraps(a_func) def wrapTheFunction(): print("Я выполняю скучную работу перед выполнением a_func()") a_func() print("Я выполняю некоторую скучную работу после выполнения a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Эй, йо! Декорируй меня!""" print("Я — функция, которой нужно немного декорирования, чтобы " "убрать мой неприятный запах") print(a_function_requiring_decoration.__name__) # Вывод: a_function_requiring_decoration

Теперь намного лучше. Далее давайте изучим некоторые распространённые случаи использования декораторов.

Спецификация шаблона:

from functools import wraps def decorator_name(f): @wraps(f) def decorated(*args, **kwargs): if not can_run: return "Функция не будет запущена" return f(*args, **kwargs) return decorated @decorator_name def func(): return("Функция выполняется") can_run = True print(func()) # Вывод: Функция выполняется can_run = False print(func()) # Вывод: Функция не будет запущена

Примечание:@wrapsОн принимает функцию для декорирования и добавляет возможность копировать имя функции, строку документации, список аргументов и так далее. Это позволяет нам получать доступ к атрибутам функции до декорирования внутри декоратора.


Сценарии использования

Теперь давайте посмотрим, в чём декораторы действительно сильны, и как их использование упрощает управление некоторыми вещами.

Authorization

Декораторы могут помочь проверить, имеет ли кто-то право использовать конечную точку веб-приложения. Они активно используются в веб-фреймворках Flask и Django. Вот пример использования авторизации на основе декораторов:

from functools import wraps def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated

Логирование

Логирование — ещё одна яркая сторона использования декораторов. Вот пример:

from functools import wraps def logit(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " была вызвана") return func(*args, **kwargs) return with_logging @logit def addition_func(x): """Выполните некоторые вычисления.""" return x + x result = addition_func(4) # Вывод: addition_func была вызвана

Уверен, вы уже придумываете ещё одно умное применение декораторов.


Декораторы с параметрами

Подумайте об этом: разве @wraps тоже не декоратор? Но он принимает параметр, как и любая обычная функция. Так почему бы и нам не сделать то же самое? Это потому, что когда вы используете синтаксис @my_decorator, вы применяете функцию-обёртку, которая принимает одну функцию в качестве аргумента. Помните, в Python всё является объектом, и это включает функции! Учитывая это, мы можем написать функцию, которая возвращает функцию-обёртку.

Встраивание декораторов в функции

Давайте вернемся к примеру с логированием и создадим функцию-обёртку, которая позволяет указать лог-файл для вывода.

from functools import wraps def logit(logfile='out.log'): def logging_decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " был вызван" print(log_string) # Открыть лог-файл и записать содержимое with open(logfile, 'a') as opened_file: # Теперь логировать в указанный лог-файл opened_file.write(log_string + '\n') return func(*args, **kwargs) return wrapped_function return logging_decorator @logit() def myfunc1(): pass myfunc1() # Вывод: myfunc1 была вызвана # Теперь появляется файл с именем out.log, и его содержимое — строка выше @logit(logfile='func2.log') def myfunc2(): pass myfunc2() # Вывод: myfunc2 была вызвана # Теперь появляется файл с именем func2.log, и его содержимое — строка выше

Классы декораторов

Теперь у нас есть декоратор logit, подходящий для продакшена, но когда некоторые части нашего приложения всё ещё хрупкие, исключения могут требовать более срочного внимания. Например, иногда вы просто хотите записать в файл. В других случаях вы хотите отправить проблему, которая привлекла ваше внимание, на электронную почту, а также сохранить запись в журнале. Это сценарий для использования наследования, но до сих пор мы видели только функции, используемые для создания декораторов.

К счастью, классы также можно использовать для создания декораторов. Так что теперь давайте перепишем logit с использованием класса, а не функции.

from functools import wraps class logit(object): def __init__(self, logfile='out.log'): self.logfile = logfile def __call__(self, func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " была вызвана" print(log_string) # Открыть лог-файл и записать with open(self.logfile, 'a') as opened_file: # Теперь записать в указанный файл opened_file.write(log_string + '\n') # Теперь отправить уведомление self.notify() return func(*args, **kwargs) return wrapped_function def notify(self): # logit только логирует, больше ничего не делает pass

Эта реализация имеет дополнительное преимущество — она чище, чем подход с вложенной функцией, а обёртывание функции по-прежнему использует тот же синтаксис, что и раньше:

@logit()
def myfunc1():
    pass

Теперь давайте создадим подкласс logit, чтобы добавить функциональность электронной почты (хотя тема электронной почты здесь не будет раскрыта).

class email_logit(logit): '''Версия реализации logit, которая может отправлять электронное письмо администратору при вызове функции''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_logit, self).__init__(*args, **kwargs) def notify(self): # Отправить электронное письмо на self.email # Здесь мы не будем это реализовывать pass

С этого момента @email_logit будет иметь тот же эффект, что и @logit, но в дополнение к логированию он также будет отправлять электронное письмо администратору.

Адрес оригинальной статьи: https://eastlakeside.gitbooks.io/interpy-zh/content/decorators/