Почему нельзя сделать декоратор с помощью одной функции в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему нельзя сделать декоратор с помощью одной функции в Python?
На самом деле, это неправда! Можно сделать декоратор с помощью одной функции, если её организовать как вложенные функции. Правильнее будет сказать: "Почему декоратор требует вложенных функций?". Давайте разберёмся, почему это необходимо.
Основная проблема
Декоратор должен:
- Получить функцию-оригинал (которую декорируем)
- Создать новую функцию (обёртку)
- Вернуть новую функцию вместо оригинала
- Сохранить способность вызывать оригинальную функцию
Это требует нескольких уровней функций.
Попытка с одной функцией (НЕПРАВИЛЬНО)
# НЕРАБОТАЮЩИЙ подход
def simple_decorator(func):
print(f"Декорирую {func.__name__}")
return func # Просто возвращаем оригинальную функцию
@simple_decorator
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!
# Это работает, но декоратор ничего НЕ делает!
# Он не обёртывает функцию
Проблема: Мы не можем добавить поведение вокруг функции без вложенной функции.
Правильный подход: вложенная функция
# ПРАВИЛЬНЫЙ подход с одной функцией (и вложенной функцией)
def logging_decorator(func):
# func — это функция, которую мы декорируем
# Но где её вызывать? Нужна обёртка!
def wrapper(*args, **kwargs): # Вложенная функция
print(f"Вызов: {func.__name__}")
result = func(*args, **kwargs) # Вызываем оригинальную функцию
print(f"Результат: {result}")
return result
return wrapper # Возвращаем обёртку, а не оригинал
@logging_decorator
def add(a, b):
return a + b
add(2, 3) # Это работает!
# Вывод:
# Вызов: add
# Результат: 5
Почему нужны вложенные функции?
# Визуальное объяснение
# Уровень 1: Декоратор получает функцию
def logging_decorator(func):
print(f"[Уровень 1] Получена функция: {func.__name__}")
# Уровень 2: Обёртка, которая вызывается при каждом вызове
def wrapper(*args, **kwargs):
print(f"[Уровень 2] Запускаем {func.__name__}")
result = func(*args, **kwargs)
print(f"[Уровень 2] {func.__name__} завершилась")
return result
return wrapper # Возвращаем обёртку
# Применение
@logging_decorator # Вызывается ОДИН раз при определении функции
def greet(name):
return f"Hello, {name}!"
# greet теперь указывает на wrapper!
greet("Alice") # wrapper вызывается КАЖДЫЙ раз
Аналогия: Упаковка посылки
┌─────────────────────────────────────────────┐
│ Декоратор (logging_decorator) │
├─────────────────────────────────────────────┤
│ │
│ Вход: def greet(name): │
│ │
│ 1. Декоратор получает оригинальную функцию│
│ 2. Создаёт НОВУЮ функцию (wrapper) │
│ 3. wrapper содержит оригинальную функцию │
│ 4. wrapper добавляет логику вокруг │
│ 5. Возвращает wrapper вместо оригинала │
│ │
│ Выход: def wrapper(*args, **kwargs): │
│ │
└─────────────────────────────────────────────┘
Практические примеры
1. Таймер (timing decorator)
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"⏱️ {func.__name__} выполнена за {elapsed:.4f} сек")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(1)
return "Готово"
slow_function() # ⏱️ slow_function выполнена за 1.0001 сек
2. Кеширование (caching decorator)
def memoize_decorator(func):
cache = {} # Словарь для кеширования результатов
def wrapper(*args, **kwargs):
# Создаём ключ кеша
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
print(f"📦 Из кеша: {func.__name__}")
return cache[key]
print(f"🔄 Вычисляю: {func.__name__}")
result = func(*args, **kwargs)
cache[key] = result
return result
return wrapper
@memoize_decorator
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(5)) # 🔄 Много вычислений
print(fibonacci(5)) # 📦 Из кеша
3. Проверка прав доступа
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if user.role != 'admin':
raise PermissionError(f"User {user.name} is not admin")
print(f"✅ {user.name} имеет доступ")
return func(user, *args, **kwargs)
return wrapper
class User:
def __init__(self, name, role):
self.name = name
self.role = role
@requires_admin
def delete_user(user, target_id):
print(f"Удаляю пользователя {target_id}")
return f"User {target_id} deleted"
admin = User("Alice", "admin")
user = User("Bob", "user")
delete_user(admin, 123) # ✅ Alice имеет доступ, Удаляю пользователя 123
try:
delete_user(user, 123) # ❌ PermissionError
except PermissionError as e:
print(f"Ошибка: {e}")
4. Повтор при ошибке (retry decorator)
import time
import random
def retry(max_attempts=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
print(f"Попытка {attempt}/{max_attempts}...")
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
print(f"❌ Не удалось после {max_attempts} попыток")
raise
print(f"⚠️ Ошибка: {e}, жду {delay}сек...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unstable_api():
if random.random() < 0.8:
raise ConnectionError("API не доступен")
return "✅ Успешно"
try:
result = unstable_api()
print(result)
except ConnectionError:
print("Окончательная ошибка")
5. Валидация аргументов
def validate_args(func):
def wrapper(*args, **kwargs):
# Проверяем аргументы
if not isinstance(args[0], (int, float)):
raise TypeError(f"Первый аргумент должен быть числом, получен {type(args[0])}")
return func(*args, **kwargs)
return wrapper
@validate_args
def square(x):
return x ** 2
print(square(5)) # 25
try:
print(square("text")) # TypeError
except TypeError as e:
print(f"Ошибка: {e}")
Декоратор с параметрами (требует ещё один уровень вложенности)
# Три уровня: параметры → декоратор → обёртка
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
results = []
for i in range(times):
print(f"Запуск {i+1}/{times}")
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=3) # repeat(times=3) возвращает decorator
def greet(name): # decorator(greet) возвращает wrapper
return f"Hello, {name}!"
print(greet("Alice"))
# Запуск 1/3
# Запуск 2/3
# Запуск 3/3
# ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']
Почему именно вложенные функции?
1. Closures (замыкания)
def outer(x):
def inner(y):
return x + y # inner помнит x из outer
return inner
add_5 = outer(5)
print(add_5(3)) # 8 (помнит x=5)
print(add_5(10)) # 15 (всё ещё помнит x=5)
2. Каждый декоратор создаёт свою область видимости
def logging_decorator(func):
# func доступна в closure
def wrapper(*args, **kwargs):
# func всё ещё доступна здесь
return func(*args, **kwargs)
return wrapper
Попытка сделать без вложенных функций (НЕ РАБОТАЕТ)
# НЕПРАВИЛЬНО
original_function = None
def bad_decorator(func):
global original_function
original_function = func # Где хранить?
return original_function
# Проблемы:
# 1. Глобальные переменные — плохая практика
# 2. Нет способа добавить логику вокруг функции
# 3. Конфликты при нескольких декораторах
Лучшие практики
✓ Используйте functools.wraps для сохранения метаданных:
import functools
def my_decorator(func):
@functools.wraps(func) # Копирует __name__, __doc__ и т.д.
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
✓ Используйте типизацию:
from typing import Callable, Any
def typed_decorator(func: Callable[..., Any]) -> Callable[..., Any]:
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
✓ Документируйте декораторы:
def useful_decorator(func):
"""Декоратор, который делает полезные вещи.
Args:
func: Функция для декорирования
Returns:
Обёрнутая функция
"""
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Вложенные функции — это не обёртка, это архитектурная необходимость. Внешняя функция получает оригинальную функцию, внутренняя функция вызывает её с логикой вокруг. Это фундаментальный паттерн Python.