Декораторы Python

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

Базовый декоратор

Декоратор в своей простейшей форме — это функция, которая принимает другую функцию в качестве аргумента и возвращает обертку (wrapper). Следующий пример показывает создание декоратора и его использование.

# Декоратор: функция, которая принимает другую функцию и возвращает обертку
def your_decorator(func):
  def wrapper():
    # Сделать что-то до вызова func...
    print("Before func!")
    func()  # Вызвать исходную функцию
    # Сделать что-то после вызова func...
    print("After func!")
  return wrapper  # Вернуть функцию-обертку

# @your_decorator — это синтаксический сахар для: foo = your_decorator(foo)
@your_decorator
def foo():
  print("Hello World!")

foo()  # Вызывает wrapper, который вызывает foo с дополнительным поведением
Before func!
Hello World!
After func!
Викторина

Войдите в систему, чтобы ответить на эту викторину и отслеживать свой прогресс обучения

Что такое декоратор в Python?
A. Функция, которая принимает другую функцию и возвращает функцию-обертку
B. Особый тип класса
C. Встроенное ключевое слово Python
D. Способ удаления функций

Декоратор для функции с параметрами

# Декоратор, который работает с функциями, имеющими параметры
def your_decorator(func):
  def wrapper(*args,**kwargs):  # Принимает любые аргументы
    # Сделать что-то до вызова func...
    print("Before func!")
    func(*args,**kwargs)  # Передать аргументы исходной функции
    # Сделать что-то после вызова func...
    print("After func!")
  return wrapper

@your_decorator
def foo(bar):
  print("My name is " + bar)

foo("Jack")  # Аргументы передаются через wrapper
Before func!
My name is Jack
After func!

Шаблон для базового декоратора

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

import functools

# Шаблон декоратора с лучшими практиками: сохраняет метаданные функции и возвращаемое значение
def your_decorator(func):
  @functools.wraps(func)  # Сохраняет имя функции, docstring и т. д.
  def wrapper(*args,**kwargs):
    # Сделать что-то до вызова func...
    result = func(*args,**kwargs)  # Вызвать функцию и захватить возвращаемое значение
    # Сделать что-то после вызова func..
    return result  # Вернуть возвращаемое значение исходной функции
  return wrapper
Викторина

Войдите в систему, чтобы ответить на эту викторину и отслеживать свой прогресс обучения

Что делает @functools.wraps(func) в декораторе?
A. Заставляет декоратор выполняться быстрее
B. Сохраняет метаданные исходной функции (имя, docstring и т. д.)
C. Предотвращает вызов функции
D. Преобразует функцию в класс

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

Вы также можете определить параметры для использования декоратором.

import functools

# Фабрика декораторов: возвращает декоратор на основе параметров
def your_decorator(arg):
  def decorator(func):
    @functools.wraps(func)  # Сохранить метаданные функции
    def wrapper(*args,**kwargs):
      # Сделать что-то до вызова func, возможно, используя arg...
      result = func(*args,**kwargs)
      # Сделать что-то после вызова func, возможно, используя arg...
      return result
    return wrapper
  return decorator  # Вернуть фактическую функцию-декоратор

Чтобы использовать этот декоратор:

# Использование декоратора с параметрами: @your_decorator(arg='x') вызывает your_decorator('x')
# который возвращает декоратор, который затем применяется к foo
@your_decorator(arg = 'x')
def foo(bar):
  return bar

Декораторы на основе классов

Чтобы декорировать метод класса, вы должны определить декоратор внутри класса. Когда методу передается только неявный аргумент self, без каких-либо явных дополнительных аргументов, вы должны создать отдельный декоратор только для этих методов без каких-либо дополнительных аргументов. Пример этого показан ниже, когда вы хотите перехватывать и выводить исключения определенным образом.

# Декоратор метода класса: определяется внутри класса
class DecorateMyMethod:

  # Декоратор статического метода для методов, имеющих только параметр 'self'
  def decorator_for_class_method_with_no_args(method):
    def wrapper_for_class_method(self):  # Принимает только self
      try:
        return method(self)  # Вызвать исходный метод
      except Exception as e:
        print("\nWARNING: Please make note of the following:\n")
        print(e)
    return wrapper_for_class_method

  def __init__(self,succeed:bool):
    self.succeed = succeed

  @decorator_for_class_method_with_no_args
  def class_action(self):
    if self.succeed:
      print("You succeeded by choice.")
    else:
      raise Exception("Epic fail of your own creation.")

test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
You succeeded by choice.
test_fail = DecorateMyMethod(False)
test_fail.class_action()
Exception: Epic fail of your own creation.

Декоратор также может быть определен как класс вместо метода. Это полезно для поддержания и обновления состояния, как в следующем примере, где мы подсчитываем количество вызовов метода:

# Декоратор на основе класса: поддерживает состояние между вызовами
class CountCallNumber:

  def __init__(self, func):
    self.func = func  # Сохранить декорируемую функцию
    self.call_number = 0  # Инициализировать счетчик вызовов

  def __call__(self, *args, **kwargs):  # Делает экземпляр вызываемым
    self.call_number += 1  # Увеличить счетчик
    print("This is execution number " + str(self.call_number))
    return self.func(*args, **kwargs)  # Вызвать исходную функцию

@CountCallNumber  # Создает экземпляр CountCallNumber
def say_hi(name):
  print("Hi! My name is " + name)

say_hi("Jack")  # Вызывает CountCallNumber.__call__()
This is execution number 1
Hi! My name is Jack
say_hi("James")
This is execution number 2
Hi! My name is James

Пример подсчета

Этот пример подсчета вдохновлен видеоуроком Патрика Лобера на YouTube.

Связанные ссылки