Декораторы Python: Волшебство в вашем коде

Декораторы широко распространены в мире Python. Их можно увидеть в Django, Flask, FastAPI и т.д. Вы также можете создавать свои собственные или использовать встроенные декораторы. Поэтому полезно знать, как они работают, чтобы лучше понимать, как они используются, например, в веб-фреймворках.

https://t.me/data_analysis_ml – наш телеграм канал Анализ Данных на python, изучайте программирование на практике.

Декоратор – это вызываемая функция, которая принимает в качестве аргумента другую функцию (декорируемую функцию).

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

Основы

def greeter(say_hi):
    def wrapper():
        print("running wrapper() start")
        print(say_hi)
        print("running wrapper() end")

    return wrapper()


greeter("Hey buddy")

"""
running wrapper() start
Hey buddy
running wrapper() end
"""

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

def greeter(say_hi):
    def wrapper():
        print("running wrapper() start")
        print(say_hi)
        print("running wrapper() end")

    return wrapper


result = greeter("Hey buddy")
print(result)  # <function greeter.<locals>.wrapper at 0x1022909a0>

Мы удалили вызов функции и просто получаем ссылку на функцию.

def greeter(say_hi):
    def wrapper():
        print("running wrapper() start")
        print(say_hi)
        print("running wrapper() end")

    return wrapper

result = greeter("Hey buddy")
result()

Теперь мы вызвали функцию вручную. Но какой в этом смысл. Давайте пойдем дальше.

def greeter(func):
    def wrapper():
        print("running wrapper() start")
        func()
        print("running wrapper() end")

    return wrapper

def friend():
    print("Hey buddy")

def pet():
    print("Woof woof")

result = greeter(friend)
result()

print("-" * 26)

result = greeter(pet)
result()

"""
running wrapper() start
Hey buddy
running wrapper() end
--------------------------
running wrapper() start
Woof woof
running wrapper() end
"""

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

Декораторы Python: Волшебство в вашем коде

Есть небольшая загвоздка… Если мы добавим в функцию параметр, то это приведет к ошибке.

Поэтапное устранение недостатков

def greeter(func):
    def wrapper():
        print("running wrapper() start")
        func()
        print("running wrapper() end")

    return wrapper


@greeter
def friend(say_hi):
    print(say_hi)


friend("Hello buddy")  # TypeError: greeter.<locals>.wrapper() takes 0 positional arguments but 1 was given

Чтобы исправить это, необходимо обновить функцию-обертку.

Функция с параметром

def greeter(func):
    def wrapper(x):  # Add x to fix the issue
        print("running wrapper() start")
        func(x)  # Add x 
        print("running wrapper() end")

    return wrapper


@greeter
def friend(say_hi):
    print(say_hi)


friend("Hello buddy")

"""
running wrapper() start
Hello buddy
running wrapper() end
"""

Но здесь снова есть небольшая загвоздка. Если мы добавим обратно нашу функцию pet без параметра, то она потерпит неудачу.

Функция без параметра

def greeter(func):
    def wrapper(x)
        print("running wrapper() start")
        func(x)
        print("running wrapper() end")

    return wrapper


@greeter
def pet():
    print("Woof woof")


pet()  # TypeError: greeter.<locals>.wrapper() missing 1 required positional argument: 'x'

Чтобы сделать его функциональным, мы можем использовать args и kwargs.

Решение с учетом параметров

def greeter(func):
    def wrapper(*args, **kwargs):
        print("running wrapper() start")
        func(*args, **kwargs)
        print("running wrapper() end")

    return wrapper


@greeter
def friend(say_hi):
    print(say_hi)


@greeter
def pet():
    print("Woof woof")


friend("Hey buddy")
print("-" * 26)
pet()

"""
running wrapper() start
Hey buddy
running wrapper() end
--------------------------
running wrapper() start
Woof woof
running wrapper() end
"""

К сожалению, если вы хотите вернуть что-то из функции, это не сработает.

Окончательная версия

def greeter(func):
    def wrapper(*args, **kwargs):
        print("running wrapper() start")
        value = func(*args, **kwargs)
        print("running wrapper() end")
        return value # without this it won't return any value from friend and pet function

    return wrapper


@greeter
def friend(say_hi):
    print(say_hi)
    return "I am Marc"


@greeter
def pet():
    print("Woof woof")
    return "I am Lina"


name = friend("Hey buddy")
print(name)  # I am Marc
print("-" * 26)
name = pet()
print(name)  # I am Lina

Пример

import time


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        value = func(*args, **kwargs)
        total = time.time() - start
        print(f"Time: {total}")

        return value

    return wrapper


def fib(n):
    if n < 2:
        return n

    return fib(n - 1) + fib(n - 2)


@timer
def main():
    fib(35)


main()

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

Когда Python запускает декораторы

greetings = []


def register(func):
    print(f"running register({func})")
    greetings.append(func)
    return func


@register
def foreigner():
    print("Good day sir")


@register
def friend():
    print("hey buddy")


def girlfriend():
    print("Hey honey")


def main():
    print("Let's begin")
    print("greetings", greetings)
    foreigner()
    friend()
    girlfriend()


if __name__ == "__main__":
    main()

"""
running greeter(<function foreigner at 0x10456c9a0>)
running greeter(<function friend at 0x10456dbc0>)
Let's begin
greetings [<function foreigner at 0x10456c9a0>, <function friend at 0x10456dbc0>]
Good day sir
hey buddy
Hey honey
"""

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

Встроенные декораторы

Кэш

import functools


@functools.cache  # @functools.lru_cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)


if __name__ == '__main__':
    print(fibonacci(50))

Попробуйте вызвать без декоратора кэша, это займет очень много времени.

functools.cache был добавлен в Python 3.9. Если вам необходимо выполнить эти примеры в Python 3.8, замените @cache на @lru_cache.

functools.cache может занимать всю доступную память при очень большом количестве записей в кэше. Я считаю его более подходящим для использования в короткоживущих сценариях командной строки. В длительно работающих процессах я рекомендую использовать functools.lru_cache с подходящим параметром maxsize.

Подробнее о lru_cache

Основное преимущество @lru_cache заключается в том, что использование памяти ограничено параметром maxsize, который по умолчанию имеет довольно консервативное значение 128, что означает, что в кэше в любой момент времени будет храниться не более 128 записей.

LRU расшифровывается как Least Recently Used, что означает, что старые записи, которые не были прочитаны в течение некоторого времени, удаляются, чтобы освободить место для новых.

Спасибо что читаете!

+1
0
+1
3
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *