Уроки модуля (4/4)
Декораторы: функции, которые изменяют функции.
Декоратор — это функция, которая принимает другую функцию и возвращает «украшенная» версия. Это мощный способ добавить поведение (регистрация, синхронизация, кэширование, повтор, проверка), не касаясь тела исходная функция.
Функции как значения
В Python функции — это объекты: вы можете передавать их, возвращать, назначать.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Минималистичный декоратор
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) записывается более элегантно с помощью @:
@log
def somma(a, b):
return a + bЭто точно то же самое. @log выше определения эквивалентно:
«как только эта функция будет определена, замените ее на log(function)».
*args, **kwargs: принять любую подпись
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args собирает позиционные аргументы, **kwargs аргументы ключевого слова. Они позволяют
вы пишете обертки, которые работают с любой функцией.
functools.wraps: сохранить метаданные
Без wraps декорированная функция теряет __name__, __doc__, подсказки:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperПример: @timeit
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
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) получает фактические аргументы, передаваемые декорированной функции во время вызовов.
Попробуйте сами
Напишите декоратор double_result, который вызывает декорированную функцию и возвращает ее результат * 2. Примените его к функции f(x)`, которая возвращает x + 1. Оцените `f(10)`.
Показать подсказку
Обертка вызывает функцию func и возвращает результат, умноженный на 2.
Решение доступно после 3 попыток
Обзор упражнения
Используйте functools.lru_cache для ускорения рекурсивной функции fib(n). Вычислите fib(30) и присвойте значение `r`. Оцените `r`.
Показать подсказку
@lru_cache(maxsize=None) выше определения.
Решение доступно после 3 попыток
Дополнительная задача
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 попыток