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

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

Привет! В прошлых статьях мы научились создавать простые декораторы. Но что, если нужно передать в декоратор параметры? Например, количество повторений, время задержки или уровень логирования? Здесь нам понадобятся декораторы с аргументами.

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

  • Что такое декораторы с аргументами
  • Структура декоратора с аргументами
  • Практические примеры
  • Отличия от декораторов без аргументов

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

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

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

Совет: Декораторы с аргументами — это декораторы, которые принимают параметры и возвращают настоящий декоратор.

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

Структура декоратора с аргументами

def decorator_with_args(arg1, arg2):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # ДО вызова
            result = func(*args, **kwargs)
            # ПОСЛЕ вызова
            return result
        return wrapper
    return decorator

Три уровня:

  1. Внешняя функция принимает аргументы декоратора
  2. Средняя функция принимает декорируемую функцию
  3. Внутренняя функция wrapper обёртывает вызов

Совет: Это функция, которая возвращает декоратор, который возвращает обёртку.

Декоратор с аргументом (повторение)

from functools import wraps

def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hello():
    print("Привет!")

say_hello()
# Привет!
# Привет!
# Привет!

Совет: Аргумент times определяет, сколько раз выполнится функция.

Декоратор с задержкой (delay)

import time
from functools import wraps

def delay(seconds):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            time.sleep(seconds)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@delay(seconds=2)
def slow_message():
    print("Сообщение с задержкой в 2 секунды")

slow_message()   # Выведет через 2 секунды

Декоратор с условным выполнением

def condition(should_run):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if should_run:
                return func(*args, **kwargs)
            else:
                print("Функция отключена")
                return None
        return wrapper
    return decorator

@condition(should_run=True)
def active_function():
    print("Функция выполнена")

@condition(should_run=False)
def inactive_function():
    print("Это не будет выполнено")

active_function()    # Функция выполнена
inactive_function()  # Функция отключена

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

def log(level="INFO"):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] Вызов {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log(level="DEBUG")
def debug_function():
    print("Отладка")

@log(level="ERROR")
def error_function():
    print("Ошибка")

debug_function()   # [DEBUG] Вызов debug_function
error_function()   # [ERROR] Вызов error_function

Совет: Аргументы декоратора позволяют настраивать его поведение.

Декоратор с аргументами по умолчанию

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

@retry(times=5, delay=0.5)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise ValueError("Ошибка!")
    return "Успешно!"

print(unstable_function())

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

ТипСинтаксисСтруктура
Без аргументов@decoratordef decorator(func): def wrapper(): ... return wrapper
С аргументами@decorator(arg)def decorator(arg): def inner(func): def wrapper(): ... return inner

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

Задача 1. Напиши декоратор repeat(times), который повторяет функцию times раз.

Задача 2. Напиши декоратор delay(seconds), который добавляет задержку перед выполнением.

Задача 3. Напиши декоратор log(level), который выводит сообщение с указанным уровнем.

Задача 4. Напиши декоратор retry(times, delay), который повторяет функцию при ошибке.

Задача 5. Напиши декоратор condition(should_run), который выполняет функцию только если should_run == True.

Ответы:

Задача 1.

def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

Задача 2.

def delay(seconds):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            time.sleep(seconds)
            return func(*args, **kwargs)
        return wrapper
    return decorator

Задача 3.

def log(level="INFO"):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

Задача 4.

def retry(times=3, delay=1):
    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(delay)
            return None
        return wrapper
    return decorator

Задача 5.

def condition(should_run):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if should_run:
                return func(*args, **kwargs)
            return None
        return wrapper
    return decorator

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

Аргументы декоратора vs аргументы функции

@repeat(times=3)   # Аргумент декоратора
def say_hello(name):   # Аргумент функции
    print(f"Привет, {name}!")

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

from functools import wraps

def repeat(times):
    def decorator(func):
        @wraps(func)   # Сохраняем метаданные
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

Синтаксис с аргументами

Декораторы с аргументами всегда вызываются с круглыми скобками, даже если аргументов нет:

# Ошибка! Аргументы обязательны
@repeat
def func():
    pass

# Правильно
@repeat(times=3)
def func():
    pass

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

Ошибка 1: Забыл вернуть wrapper

def repeat(times):
    def decorator(func):
        # Забыли return wrapper
    return decorator

Ошибка 2: Неправильный порядок декораторов

@timer
@repeat(3)
def func():
    pass

Ошибка 3: Аргументы по умолчанию изменяемые

# Плохо
def decorator(arg=[]):
    pass

Шпаргалка

Тип декоратораСинтаксисСтруктура
Без аргументов@decoratordef decorator(func): ...
С аргументами@decorator(arg)def decorator(arg): def inner(func): ...

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

Сегодня мы:

  • Научились создавать декораторы с аргументами
  • Разобрали структуру декораторов с аргументами
  • Написали практические примеры
  • Поняли отличие от декораторов без аргументов

КВИЗ

Что дальше?

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