Перейти к основному содержимому
Модуль 9 · Урок 4 из 436/36 в курсе~14 min
Уроки модуля (4/4)

Декораторы: функции, которые изменяют функции.

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

Функции как значения

В Python функции — это объекты: вы можете передавать их, возвращать, назначать.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Минималистичный декоратор

Python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"chiamo {func.__name__}")
        risultato = func(*args, **kwargs)
        print(f"ho finito {func.__name__}")
        return risultato
    return wrapper

def somma(a, b):
    return a + b

somma = log(somma)   # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5

Синтаксис @decorator

Присвоение somma = log(somma) записывается более элегантно с помощью @:

Python
@log
def somma(a, b):
    return a + b

Это точно то же самое. @log выше определения эквивалентно: «как только эта функция будет определена, замените ее на log(function)».

*args, **kwargs: принять любую подпись

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args собирает позиционные аргументы, **kwargs аргументы ключевого слова. Они позволяют вы пишете обертки, которые работают с любой функцией.

functools.wraps: сохранить метаданные

Без wraps декорированная функция теряет __name__, __doc__, подсказки:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Пример: @timeit

Python
from functools import wraps
import time

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        risultato = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
        return risultato
    return wrapper

@timeit
def somma_grande():
    return sum(range(10**6))

somma_grande()

Декораторы из stdlib

Python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

fib(100)  # istantaneo grazie alla cache
  • @lru_cache — запоминает результаты чистой функции.
  • @property — предоставляет метод как атрибут (см. модуль ООП).
  • @staticmethod, @classmethod — изменить способ передачи self.

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

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

Попробуйте сами

Упражнение#python.m9.l4.e1
Попыток: 0Загрузка…

Напишите декоратор double_result, который вызывает декорированную функцию и возвращает ее результат * 2. Примените его к функции f(x)`, которая возвращает x + 1. Оцените `f(10)`.

Загрузка редактора…
Показать подсказку

Обертка вызывает функцию func и возвращает результат, умноженный на 2.

Решение доступно после 3 попыток

Обзор упражнения

Упражнение#python.m9.l4.e2
Попыток: 0Загрузка…

Используйте functools.lru_cache для ускорения рекурсивной функции fib(n). Вычислите fib(30) и присвойте значение `r`. Оцените `r`.

Загрузка редактора…
Показать подсказку

@lru_cache(maxsize=None) выше определения.

Решение доступно после 3 попыток

Дополнительная задача

Упражнение#python.m9.l4.e3
Попыток: 0Загрузка…

Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.

Загрузка редактора…
Показать подсказку

Обертка вызывает func(*args, **kwargs) и умножает результат на 2.

Решение доступно после 3 попыток