Что такое декоратор: Декораторы в Python: понять и полюбить

Путь к пониманию декораторов в Python / Хабр

Прим. Wunder Fund: В этой статье разбираемся, что такое декораторы в Python, зачем они нужны, и в чем их прикол. Статья будет полезна начинающим разработчикам.

Материал рассчитан на начинающих программистов, которые хотят разобраться с тем, что такое декораторы, и с тем, как применять их в своих проектах.

Что такое декораторы?

Декораторы — это обёртки вокруг Python-функций (или классов), которые изменяют работу того, к чему они применяются. Декоратор максимально абстрагирует собственные механизмы. А синтаксические конструкции, используемые при применении декораторов, устроены так, чтобы они как можно меньше влияли бы на код декорируемых сущностей. Разработчики могут создавать код для решения своих специфических задач так, как привыкли, а декораторы могут использовать исключительно для расширения функционала своих разработок. Всё это — очень общие утверждения, поэтому перейдём к примерам.

В Python декораторы используются, в основном, для декорирования функций (или, соответственно, методов). Возможно, одним из самых распространённых декораторов является декоратор @property:

class Rectangle:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    @property
    def area(self):
        return self.a * self.b
rect = Rectangle(5, 6)
print(rect.area)
# 30

В последней строке кода, мы можем обратиться к члену area экземпляра класса Rectangle как к атрибуту. То есть — нам не нужно вызывать метод area. Вместо этого при обращении к area как к атрибуту (то есть — без использования скобок, ()), соответствующий метод вызывается неявным образом. Это возможно благодаря декоратору @property.

Как работают декораторы?

Размещение конструкции @property перед определением функции равносильно использованию конструкции вида area = property(area). Другими словами, property — это функция, которая принимает другую функцию в качестве аргумента и возвращает ещё одну функцию. Именно этим и занимаются декораторы.

В результате оказывается, что декоратор меняет поведение декорируемой функции.

Декораторы функций

Декоратор retry

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

Предположим, имеется функция, которую мы хотим запустить повторно в том случае, если при её первом запуске произойдёт сбой. То есть — нам нужна функция (декоратор, имя которого, retry, можно перевести как «повтор»), которая вызывает нашу функцию один или два раза (это зависит от того, возникнет ли ошибка при первом вызове функции).

В соответствии с ранее данным определением — мы можем сделать код нашего простого декоратора таким:

def retry(func):
    def _wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except:
            time. sleep(1)
            func(*args, **kwargs)
    return _wrapper
@retry
def might_fail():
    print("might_fail")
    raise Exception
might_fail()

Наш декоратор носит имя retry. Он принимает в виде аргумента (func) любую функцию. Внутри декоратора определяется новая функция (_wrapper), после чего осуществляется возврат этой функции. Тому, кто впервые видит код декоратора, может показаться непривычным объявление одной функции внутри другой функции. Но это — совершенно корректная синтаксическая конструкция, следствием применения которой является тот полезный для нас факт, что функция _wrapper видна лишь внутри пространства имён декоратора 

retry.

Обратите внимание на то, что в этом примере мы декорируем функцию might_fail() с использованием конструкции, которая выглядит @retry. После имени декоратора нет круглых скобок. В результате получается, что когда мы, как обычно, вызываем функцию might_fail(), на самом деле, вызывается декоратор retry, которому передаётся, в виде первого аргумента, целевая функция (might_fail).

Получается, что, в общей сложности, тут мы поработали с тремя функциями:

  • retry

  • _wrapper

  • might_fail

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

@retry, а не @retry(). Подытожим:

  • Декоратор — это всего лишь функция (которая, в качестве аргумента, принимает другую функцию).

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

Следовательно, мы можем ввести в код четвёртую функцию, которая принимает параметр, с помощью которого мы хотим настраивать поведение декоратора, и возвращает функцию, которая и является декоратором (то есть — принимает в качестве аргумента другую функцию).

Попробуем такую конструкцию:

def retry(max_retries):
    def retry_decorator(func):
        def _wrapper(*args, **kwargs):
            for _ in range(max_retries):
                try:
                    func(*args, **kwargs)
                except:
                    time.sleep(1)
        return _wrapper
    return retry_decorator
@retry(2)
def might_fail():
    print("might_fail")
    raise Exception
might_fail()

Разберём этот код:

  • На первом уровне тут имеется функция retry.

  • Функция retry принимает произвольный аргумент (в нашем случае — max_retries) и возвращает другую функцию — retry_decorator.

  • Функция retry_decorator — это и есть реальный декоратор.

  • Функция _wrapper работает так же, как и прежде (только теперь она руководствуется сведениями о максимальном количестве перезапусков декорированной функции).

О коде нового декоратора мне больше сказать нечего. Теперь поговорим об его использовании:

  • Функция might_fail теперь декорируется с помощью вызова функции вида @retry(2).

  • Вызов retry(2) приводит к тому, что вызывается функция retry, которая и возвращает реальный декоратор.

  • В итоге функция might_fail декорируется с помощью retry_decorator, так как именно эта функция представляет собой результат вызова функции retry(2).

Декоратор timer

Напишем ещё один полезный декоратор — timer («таймер»). Он будет измерять время выполнения декорированной с его помощью функции:

import functools
import time
def timer(func):
    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        runtime = time.perf_counter() - start
        print(f"{func.__name__} took {runtime:.4f} secs")
        return result
    return _wrapper
@timer
def complex_calculation():
    """Some complex calculation.
""" time.sleep(0.5) return 42 print(complex_calculation())

Вот результаты выполнения этого кода:

complex_calculation took 0.5041 secs
42

Видно, что декоратор timer выполняет какой-то код до и после вызова декорируемой функции. В остальном же он работает точно так же, как декоратор, рассмотренный в предыдущем разделе. Но при его написании мы воспользовались и кое-чем новым.

Декоратор functools.wraps

Анализируя вышеприведённый код, вы могли заметить, что сама функция _wrapper декорирована с помощью @functools.wraps. Но это никоим образом не меняет логику или функционал декоратора 

timer. При этом разработчик может принять решение о целесообразности использования functools.wraps.

Но, так как декоратор @timer может быть представлен как complex_calculation = timer(complex_calculation), он обязательно изменяет функцию complex_calculation. В частности, он меняет некоторые из отражённых магических методов:

  • __module__

  • __name__

  • __qualname__

  • __doc__

  • __annotations__

При использовании декоратора @functools. wraps эти атрибуты возвращаются к их исходному состоянию.

Вот что получится без @functools.wraps:

print(complex_calculation.__module__)       # __main__
print(complex_calculation.__name__)         # wrapper_timer
print(complex_calculation.__qualname__)     # timer.<locals>.wrapper_timer
print(complex_calculation.__doc__)          # None
print(complex_calculation.__annotations__)  # {}

А использование @functools.wraps даёт нам следующее:

print(complex_calculation.__module__)       # __main__#
print(complex_calculation.__name__)         # complex_calculation
print(complex_calculation.__qualname__)     # complex_calculation
print(complex_calculation.__doc__)          # Some complex calculation.
print(complex_calculation.__annotations__)  # {}

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

До сих пор мы обсуждали декораторы для функций. Но декорировать можно и классы.

Возьмём декоратор timer из предыдущего примера. Он отлично подходит и в качестве обёртки для класса:

@timer
class MyClass:
    def complex_calculation(self):
        time.
sleep(1) return 42 my_obj = MyClass() my_obj.complex_calculation()

Вот что нам это даст:

Finished 'MyClass' in 0.0000 secs

Видно, что здесь нет сведений о времени выполнения метода complex_calculation. Вспомним о том, что конструкция, начинающаяся с @ — это всего лишь эквивалент MyClass = timer(MyClass). То есть — декоратор вызывается только когда «вызывают» класс. «Вызов» класса — это создание его экземпляра. Получается, что timer вызывается лишь при выполнении строки кода my_obj = MyClass().

При декорировании класса методы этого класса не подвергаются автоматическому декорированию. Проще говоря — использование обычного декоратора для декорирования обычного класса приводит лишь к декорированию конструктора (метод __init__) этого класса.

Но можно поменять поведение всего класса, воспользовавшись другой формой конструктора. Правда, прежде чем об этом говорить, давайте поинтересуемся тем, может ли декоратор работать несколько иначе — то есть можно ли декорировать функцию с помощью класса.

Оказывается — это возможно:

class MyDecorator:
    def __init__(self, function):
        self.function = function
        self.counter = 0
    
    def __call__(self, *args, **kwargs):
        self.function(*args, **kwargs)
        self.counter+=1
        print(f"Called {self.counter} times")
@MyDecorator
def some_function():
    return 42
some_function()
some_function()
some_function()

Вот что получится:

Called 1 times
Called 2 times
Called 3 times

В ходе работы этого кода происходит следующее:

  • Функция __init__ вызывается при декорировании some_function. Тут, снова, не забываем о том, что использование декоратора — это аналог конструкции some_function = MyDecorator(some_function).

  • Функция __call__ вызывается при использовании экземпляра класса, например — при вызове функции. Функция some_function — это теперь экземпляр класса MyDecorator, но использовать мы её при этом планируем как функцию. За это отвечает магический метод __call__, в имени которого используются два символа подчёркивания.

Декорирование класса в Python, с другой стороны, работает путём изменения класса извне (то есть — из декоратора).

Взгляните на этот код:

def add_calc(target):
    def calc(self):
        return 42
    target.calc = calc
    return target
@add_calc
class MyClass:
    def __init__():
        print("MyClass __init__")
my_obj = MyClass()
print(my_obj.calc())

Вот что он выдаст:

MyClass __init__
42

Если вспомнить определение декоратора, то всё, что тут происходит, следует уже знакомой нам логике:

  • Вызов my_obj = MyClass() инициирует последовательность действий, которая начинается с вызова декоратора.

  • Декоратор add_calc дополняет класс методом calc.

  • В итоге создаётся экземпляр класса с использованием конструктора.

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

Использование декораторов

Декораторы в стандартной библиотеке Python

В следующих разделах мы познакомимся с несколькими наиболее популярными и наиболее широко используемыми декораторами, которые включены в состав стандартной библиотеки Python.

Декоратор property

Как уже было сказано, @property — это, скорее всего, один из самых популярных Python-декораторов. Его цель заключается в том, чтобы обеспечить доступ к результатам вызова метода класса в такой форме, как будто этот метод является атрибутом. Конечно, существует и альтернатива @property, что позволяет, при выполнении операции присваивания значения, самостоятельно выполнять вызов метода.

class MyClass:
    def __init__(self, x):
        self. x = x
    
    @property
    def x_doubled(self):
        return self.x * 2
    
    @x_doubled.setter
    def x_doubled(self, x_doubled):
        self.x = x_doubled // 2
my_object = MyClass(5) 
print(my_object.x_doubled)  #  10  
print(my_object.x)          #  5  
my_object.x_doubled = 100   #    
print(my_object.x_doubled)  #  100 
print(my_object.x)          #  50    
Декоратор staticmethod

Ещё один широко известный декоратор — это @staticmethod. Он используется в ситуациях, когда надо вызвать функцию, объявленную в классе, не создавая при этом экземпляр данного класса:

class C:
    @staticmethod
    def the_static_method(arg1, arg2):
        return 42
print(C.the_static_method())
Декоратор functools.cache

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

Например, можно соорудить нечто вроде такого кода:

_cached_result = None
def complex_calculations():
    if _cached_result is None:
        _cached_result = something_complex()
    return _cached_result

Использование глобальной переменной, вроде _cached_result, проверка её на None, запись в эту переменную некоего значения в том случае, если она не равна None — всё это — повторяющиеся задачи. А значит — перед нами идеальная ситуация для применения декораторов. Но самостоятельно писать такой декоратор нам не придётся — в стандартной библиотеке Python есть именно то, что нужно для решения этой задачи — декоратор cache:

from functools import cache
@cache
def complex_calculations():
    return something_complex()

Теперь, при попытке вызова complex_calculations(), Python, перед вызовом функции something_complex, проверяет, имеется ли кешированный результат её работы. Если результат её вызова имеется в кеше — something_complex не придётся вызывать дважды.

Декоратор dataclass

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

Модуль стандартной библиотеки dataclasses — это хороший пример механизма, применение которого в определённых ситуациях предпочтительнее применения механизмов наследования. Сначала давайте посмотрим на всё это в действии:

from dataclasses import dataclass
@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity: int = 0
    def total_cost(self) -> float:
        return self.unit_price * self.quantity
item = InventoryItem(name="", unit_price=12, quantity=100)
print(item.total_cost())    # 1200

На первый взгляд кажется, что декоратор @dataclass просто снимает с нас нагрузку по написанию конструктора класса, позволяя избежать ручного написания кода, подобного следующему:

...
    def __init__(self, name, unit_price, quantity):
        self.name = name
        self.unit_price = unit_price
        self.quantity = quantity
...

Но не всё так просто. Предположим, решено оснастить Python-проект REST-API, при этом встанет необходимость преобразовывать Python-объекты в JSON-строки.

Существует пакет dataclasses-json (не входящий в состав стандартной библиотеки), который позволяет декорировать классы данных и даёт возможность превращать объекты в их JSON-представление и выполнять обратное преобразование, производить сериализацию и десериализацию объектов.

Вот как это выглядит:

from dataclasses import dataclass
from dataclasses_json import dataclass_json
@dataclass_json
@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity: int = 0
    def total_cost(self) -> float:
        return self.unit_price * self.quantity
item = InventoryItem(name="", unit_price=12, quantity=100)
print(item.to_dict())
# {'name': '', 'unit_price': 12, 'quantity': 100}

Разбирая этот код, можно сделать два наблюдения:

  1. Декораторы могут быть вложены друг в друга. При этом важен порядок их появления в коде.

  2. Декоратор @dataclass_json добавляет к классу метод to_dict.

Конечно, можно написать миксин (mixin, подмешанный класс), ответственный за решение всех сложных задач, связанных с типобезопасной реализацией метода to_dict. Потом можно сделать наш класс InventoryItem наследником этого класса.

В предыдущем примере, однако, декоратор оснащает класс лишь техническим функционалом (в противоположность расширению возможностей класса с учётом конкретной задачи). В результате можно отключать и подключать этот декоратор, не меняя поведения основной программы. Этот подход позволяет сохранить нашу «естественную» иерархию классов, код проекта не придётся подвергать изменениям. Декоратор dataclasses-json можно добавить в проект, не переписывая при этом тела существующих методов.

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

О, а приходите к нам работать? 😏

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде.

Python | Декораторы

Последнее обновление: 08.10.2022

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

Рассмотрим простейший пример:


# определение функции декоратора
def select(input_func):    
    def output_func():      # определяем функцию, которая будет выполняться вместо оригинальной
        print("*****************")  # перед выводом оригинальной функции выводим всякую звездочки
        input_func()                # вызов оригинальной функции
        print("*****************")  # после вывода оригинальной функции выводим всякую звездочки
    return output_func     # возвращаем новую функцию

# определение оригинальной функции
@select         # применение декоратора select
def hello():
    print("Hello METANIT. COM")

# вызов оригинальной функции
hello()

Вначале определяется собственно функция декоратора, которая в данном случае называется select(). В качестве параметра декоратор получает функцию (в данном случае параметр input_func), к которой этот декоратор будет применяться:


def select(input_func):    
    def output_func():      # определяем функцию, которая будет выполняться вместо оригинальной
        print("*****************")  # перед выводом оригинальной функции выводим всякую звездочки
        input_func()                # вызов оригинальной функции
        print("*****************")  # после вывода оригинальной функции выводим всякую звездочки
    return output_func     # возвращаем новую функцию

Результатом декоратора в данном случае является локальная функция output_func, в которой вызывается входная функция input_func. Для простоты здесь перед и после вызыва input_func для красоты просто выводим набор символов «#».

Далее определяется стандартная функция, к которой применяется декоратор — в данном случае это функция hello, которая просто выводит на консоль некоторую строку:


@select         # применение декоратора select
def hello():
    print("Hello METANIT.COM")

Для применения декоратора перед определением функции указывается символ @, после которого идет имя декоратора. То есть в данном случае к функции hello() применяется декоратор select().

Далее вызываем обычную функцию:


hello()

Поскольку к этой функции применяется декоратор select, то в результате функциия hello передается в декоратор select() в качестве параметра input_func. И поскольку декоратор возвращает новую функцию — output_func, то фактически в данном случае будет выполняться именно эта функция output_func()

В итоге мы получим следующий консольный вывод:


*****************
Hello METANIT. COM
*****************

Получение параметров функции в декораторе

Декоратор может перехватывать передаваемые в функцию аргументы:


# определение функции декоратора
def check(input_func):    
    def output_func(*args):      # через *args получаем значения параметров оригинальной функции
        input_func(*args)                # вызов оригинальной функции
    return output_func     # возвращаем новую функцию

# определение оригинальной функции
@check
def print_person(name, age):
    print(f"Name: {name}  Age: {age}")

# вызов оригинальной функции
print_person("Tom", 38)

Здесь функция print_person() принимает два параметра: name (имя) и age (возраст). К этой функции применяется декоратор check()

В декораторе check возвращается локальная функция output_func(), которая принимает некоторый набор значений в виде параметра *args — это те значения, которые передаются в оригинальную функцию, к которой применяется декоратор. То есть в данном случае *args будет содержать значения параметров name и age.


def check(input_func):    
    def output_func(*args):      # через *args получаем значения параметров функции input_func

Здесь просто передаем эти значения в оригинальную функцию:

input_func(*args)  

В итоге в данном получим следующий консольный вывод

Name: Tom  Age: 38

Но что, если в функцию print_person будет передано какое-то недопустимое значение, например, отрицательный возраст? Одним из преимуществ декораторов как раз является то, что мы можем проверить и при необходимости модифицировать значения параметров. Например:


# определение функции декоратора
def check(input_func):    
    def output_func(*args):
        name = args[0]
        age = args[1]           # получаем значение второго параметра
        if age < 0: age = 1     # если возраст отрицательный, изменяем его значение на 1
        input_func(name, age)   # передаем функции значения для параметров
    return output_func

# определение оригинальной функции
@check
def print_person(name, age):
    print(f"Name: {name}  Age: {age}")

# вызов оригинальной функции
print_person("Tom", 38)
print_person("Bob", -5)

args фактически представляет список значений, и, используя индексы, мы можем получить значения параметров по позиции и что-то с ними сделать. Так, здесь, если значение возраста меньше 0, то устанавливаем 1. Затем передаем эти значения в вызов функции. В итоге здесь получим следующий вывод:


Name: Tom  Age: 38
Name: Bob  Age: 1

Получение результата функции

Подобным образом можно получить результат функции и при необходимости изменить его:


# определение функции декоратора
def check(input_func):    
    def output_func(*args):
        result = input_func(*args)   # передаем функции значения для параметров
        if result < 0: result = 0   # если результат функции меньше нуля, то возвращаем 0
        return result
    return output_func

# определение оригинальной функции
@check
def sum(a, b):
    return a + b

# вызов оригинальной функции
result1 = sum(10, 20)
print(result1)          # 30

result2 = sum(10, -20)
print(result2)          # 0

Здесь определена функция sum(), которая возвращает сумму чисел. В декораторе check проверяем результат функции и для простоты, если он меньше нуля, то возвращаем 0.

Консольный вывод программы:

НазадСодержаниеВперед

7. Декораторы — Советы по Python 0.1 документация

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

Во-первых, давайте обсудим, как написать собственный декоратор.

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

7.1. Все в Python является объектом:

Прежде всего, давайте разберемся с функциями в Python:

 def hi(name="yasoob"):
    верни "привет" + имя
распечатать(привет())
# вывод: 'привет, ясуб'
# Мы можем даже присвоить функцию переменной, например
приветствовать = привет
# Мы не используем здесь круглые скобки, потому что мы не вызываем функцию hi
# вместо этого мы просто помещаем его в переменную приветствия.  Попробуем запустить это
распечатать (приветствовать ())
# вывод: 'привет, ясуб'
# Посмотрим, что произойдет, если мы удалим старую функцию hi!
дель привет
распечатать(привет())
#выходы: ошибка имени
распечатать (приветствовать ())
#outputs: 'привет, ясуб'
 

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

Таковы основы, когда речь заходит о функциях. давайте возьмем ваш знания на шаг впереди. В Python мы можем определять функции внутри другие функции:

 def hi(name="yasoob"):
    print("теперь вы внутри функции hi()")
    приветствовать():
        вернуть «теперь вы находитесь в функции приветствия ()»
    приветствие ():
        return "теперь вы находитесь в функции welcome()"
    распечатать (приветствовать ())
    распечатать(добро пожаловать())
    print("теперь вы снова в функции hi()")
привет()
#output:теперь вы внутри функции hi()
# теперь вы находитесь в функции приветствия()
# теперь вы находитесь в функции welcome()
# теперь вы снова в функции hi()
# Это показывает, что всякий раз, когда вы вызываете hi(), Greet() и Welcome()
# также называются.  Однако функции приветствия() и приветствия()
# недоступны вне функции hi() например:
приветствовать()
#outputs: NameError: имя "greet" не определено
 

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

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

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

 def hi(name="yasoob"):
    приветствовать():
        вернуть «теперь вы находитесь в функции приветствия ()»
    приветствие ():
        return "теперь вы находитесь в функции welcome()"
    если имя == "ясуб":
        ответное приветствие
    еще:
        Добро пожаловать
а = привет()
печать (а)
#outputs: <приветствие функции по адресу 0x7f2143c01500>
#Это ясно показывает, что `a` теперь указывает на функцию приветствия() в hi()
#А теперь попробуй
распечатать (а())
#outputs: теперь вы находитесь в функции приветствия()
 

Просто взгляните на код еще раз. В предложении if/else мы возвращает приветствие и приветствие , а не приветствие() и приветствие() . Почему это? Это потому, что, когда вы ставите пару скобок после него, функция выполняется; тогда как если вы не поставите круглые скобки после него, затем его можно передать и присвоить другим переменным не выполняя его. Ты понял? Позвольте мне объяснить это в немного Подробнее. Когда мы пишем a = hi() , hi() выполняется и поскольку по умолчанию используется имя yasoob, возвращается функция приветствия . Если мы изменим выражение на a = hi(name = "ali") , то приветствуем функция будет возвращена. Мы также можем выполнить print hi()() , который выводит теперь вы находитесь в функции приветствия() .

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

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

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

7.5. Написание вашего первого декоратора:

В последнем примере мы действительно создали декоратор! Давайте изменим предыдущий декоратор и сделать программу более удобной:

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

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

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

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

 print(a_function_requiring_decoration.__name__)
# Вывод: wrapTheFunction
 

Это не то, что мы ожидали! Его имя «a_function_requiring_decoration». Итак, наша функция была заменена на обернутьФункция. Он переопределял имя и строку документации нашей функции. К счастью, Python предоставляет нам простую функцию для решения этой проблемы. это functools.wraps . Давайте изменим наш предыдущий пример, чтобы использовать functools.wraps :

 из пакетов импорта functools
деф a_new_decorator(a_func):
    @обертки(a_func)
    деф обернутьФункция():
        print("Я делаю скучную работу перед выполнением a_func()")
        a_func()
        print("Я делаю скучную работу после выполнения a_func()")
    вернуть оберткуTheFunction
@a_new_decorator
определение a_function_requiring_decoration():
    """Эй, эй! Украсьте меня!"""
    print("Я функция, которая нуждается в украшении, чтобы "
          "удали мой неприятный запах")
print(a_function_requiring_decoration. __name__)
# Вывод: a_function_requiring_decoration
 

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

Чертеж:

 из пакетов импорта functools
def декоратор_имя (f):
    @обертывания(ф)
    деф оформлен(*args, **kwargs):
        если не can_run:
            вернуть "Функция не будет работать"
        вернуть f(*args, **kwargs)
    вернуться украшенный
@decorator_name
Функция определения():
    return("Функция запущена")
can_run = Истина
печать (функция ())
# Вывод: функция запущена
can_run = Ложь
печать (функция ())
# Вывод: функция не запустится
 

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

7.5.1. Варианты использования:

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

7.5.2. Авторизация

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

Пример:

 из пакетов импорта functools
защита требует_auth (f):
    @обертывания(ф)
    деф оформлен(*args, **kwargs):
        авторизация = запрос.авторизация
        если нет авторизации или нет check_auth(auth.username, auth.password):
            аутентифицировать()
        вернуть f(*args, **kwargs)
    вернуться украшенный
 

7.6. Декораторы с аргументами

Если подумать, разве @wraps тоже не является декоратором? Но это занимает аргумент, как и любая нормальная функция. Итак, почему мы не можем сделать это тоже?

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

7.6.1. Вложение декоратора в функцию

Давайте вернемся к нашему примеру с журналированием и создадим оболочку, которая позволяет мы указываем файл журнала для вывода.

 из пакетов импорта functools
деф логит (файл журнала = 'out.log'):
    деф logging_decorator (функция):
        @обертывания (функция)
        def wrapper_function(*args, **kwargs):
            log_string = func.__name__ + "был вызван"
            печать (лог_строка)
            # Откройте лог-файл и добавьте
            с open(logfile, 'a') как open_file:
                # Теперь мы логируемся в указанный лог-файл
                open_file.write(log_string + '\n')
            функция возврата (*args, **kwargs)
        вернуть завернутую_функцию
    вернуть logging_decorator
@логит()
защита myfunc1():
    проходить
моя функция1()
# Вывод: был вызван myfunc1
# Файл с именем out. log теперь существует с приведенной выше строкой
@logit(logfile='func2.log')
защита myfunc2():
    проходить
моя функция2()
# Вывод: был вызван myfunc2
# Теперь существует файл func2.log с приведенной выше строкой
 

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

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

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

 логит класса (объект):
 _logfile = 'out.log'
    def __init__(я, функция):
        self.func = функция
 def __call__(я, *аргументы):
 log_string = self. func.__name__ + "был вызван"
        печать (лог_строка)
        # Откройте лог-файл и добавьте
 с open(self._logfile, 'a') как open_file:
            # Теперь мы логируемся в указанный лог-файл
            open_file.write(log_string + '\n')
        # Теперь отправьте уведомление
        самоуведомление()
 # возвращаем базовую функцию
 вернуть self.func(*args)
    деф уведомить (сам):
        # logit только логи, не более
        проходить
 

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

 logit._logfile = 'out2.log' # если изменить файл журнала
@логит
защита myfunc1():
    проходить
моя функция1()
# Вывод: был вызван myfunc1
 

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

 класс email_logit(logit):
    '''
    Реализация логита для отправки писем администраторам
    при вызове функции. 
    '''
    def __init__(self, email='[email protected]', *args, **kwargs):
        self.email = электронная почта
        super(email_logit, self).__init__(*args, **kwargs)
    деф уведомить (сам):
        # Отправить электронное письмо на адрес self.email
        # Здесь не будет реализовано
        проходить
 

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

Декоратор