Простейший декоратор без аргументов

Простейший декоратор без аргументов Декораторы

Привет! В прошлой статье мы познакомились с декораторами и поняли, что это такое. Теперь пришло время научиться писать свои декораторы. Начнём с самого простого — декоратора без аргументов.

В этой статье мы разберём:

  • Структуру простейшего декоратора
  • Как написать свой первый декоратор
  • Сохранение метаданных
  • Практические примеры

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный Python
  • Базовое понимание функций и декораторов (статья 1 в этом подменю)

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

Основная часть

Структура простейшего декоратора

def decorator(func):
    def wrapper(*args, **kwargs):
        # Дополнительное поведение ДО вызова функции
        result = func(*args, **kwargs)
        # Дополнительное поведение ПОСЛЕ вызова функции
        return result
    return wrapper

Три обязательных элемента:

  1. Внешняя функция decorator, принимающая func
  2. Внутренняя функция wrapper, которая обёртывает вызов
  3. Возврат wrapper из decorator

Совет: *args и **kwargs позволяют передавать любые аргументы в обёрнутую функцию.

Простейший декоратор для логирования

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Вызов функции: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Результат: {result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

@logger
def multiply(a, b):
    return a * b

print(add(3, 5))      # Вызов функции: add -> Результат: 8 -> 8
print(multiply(2, 4)) # Вызов функции: multiply -> Результат: 8 -> 8

Декоратор для замера времени

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Функция {func.__name__} выполнилась за {end - start:.4f} сек")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1.5)
    return "Готово!"

slow_function()   # Функция slow_function выполнилась за 1.5003 сек

Декоратор для проверки прав

def require_admin(func):
    def wrapper(user, *args, **kwargs):
        if not user.get("is_admin", False):
            print("Доступ запрещён")
            return None
        return func(user, *args, **kwargs)
    return wrapper

@require_admin
def delete_user(user, user_id):
    print(f"Пользователь {user_id} удалён")

admin = {"name": "admin", "is_admin": True}
user = {"name": "alice", "is_admin": False}

delete_user(admin, 123)   # Пользователь 123 удалён
delete_user(user, 456)    # Доступ запрещён

Сохранение метаданных с @wraps

Без @wraps декоратор теряет имя и документацию функции.

# Без wraps
def logger(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@logger
def greet():
    """Приветствие"""
    return "Hello"

print(greet.__name__)   # wrapper
print(greet.__doc__)    # None

С wraps:

from functools import wraps

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

@logger
def greet():
    """Приветствие"""
    return "Hello"

print(greet.__name__)   # greet
print(greet.__doc__)    # Приветствие

Совет: Всегда используй @wraps в своих декораторах. Это хорошая практика.

Декоратор для повторных попыток (retry)

import time

def retry(times=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Попытка {attempt + 1} не удалась: {e}")
                    time.sleep(1)
            return None
        return wrapper
    return decorator

@retry(times=5)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise ValueError("Случайная ошибка!")
    return "Успешно!"

print(unstable_function())

Цепочка декораторов

@timer
@logger
def add(a, b):
    return a + b

add(3, 5)
# Вывод:
# Вызов функции: add
# Результат: 8
# Функция add выполнилась за 0.0000 сек

Важно: Порядок декораторов важен. Снизу вверх: сначала @logger, потом @timer.

Задачи для закрепления

Задача 1. Напиши декоратор, который выводит сообщение до и после вызова функции.

Задача 2. Напиши декоратор, который замеряет время выполнения функции.

Задача 3. Напиши декоратор, который проверяет, что пользователь является администратором.

Задача 4. Напиши декоратор, который выводит аргументы функции.

Задача 5. Напиши декоратор, который сохраняет результат функции в кэш и при повторном вызове с теми же аргументами возвращает сохранённый результат.

Ответы:

Задача 1.

def border(func):
    def wrapper(*args, **kwargs):
        print("Начало работы")
        result = func(*args, **kwargs)
        print("Конец работы")
        return result
    return wrapper

Задача 2.

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Время: {end - start:.4f} сек")
        return result
    return wrapper

Задача 3.

def require_admin(func):
    def wrapper(user, *args, **kwargs):
        if not user.get("is_admin", False):
            return "Доступ запрещён"
        return func(user, *args, **kwargs)
    return wrapper

Задача 4.

def debug(func):
    def wrapper(*args, **kwargs):
        print(f"Аргументы: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

Задача 5.

def cache(func):
    cache_storage = {}
    def wrapper(*args, **kwargs):
        key = (args, tuple(kwargs.items()))
        if key not in cache_storage:
            cache_storage[key] = func(*args, **kwargs)
        return cache_storage[key]
    return wrapper

@cache
def slow_square(x):
    import time
    time.sleep(1)
    return x ** 2

print(slow_square(2))   # 4 (ждём 1 сек)
print(slow_square(2))   # 4 (мгновенно)

Нюансы и подводные камни

Сохранение метаданных

Всегда используй @wraps для сохранения имени и документации.

Порядок декораторов

@decorator1
@decorator2
def func():
    pass

# Эквивалентно
func = decorator1(decorator2(func))

Декоратор без возврата функции

Если декоратор не возвращает функцию, исходная функция будет заменена на None.

Частые ошибки и как их избежать

Ошибка 1: Забыл *args и **kwargs

Всегда используй *args и **kwargs для поддержки любых аргументов.

Ошибка 2: Забыл @wraps

Используй @wraps для сохранения метаданных.

Ошибка 3: Декоратор не возвращает функцию

Всегда возвращай wrapper.

Шпаргалка

Что нужноКак пишется
Декоратор без аргументовdef decorator(func): ... return wrapper
С сохранением метаданныхfrom functools import wraps
Применение декоратора@decorator
Порядок декораторовСнизу вверх: @deco1 @deco2

Заключение / Выводы

Сегодня мы:

  • Научились писать простейшие декораторы
  • Разобрали структуру декоратора
  • Познакомились с @wraps
  • Решали практические задачи

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×