📜 Как показано в исходном материале: кэш вручную
def cache(func):
memo = {}
def wrapper(*args):
if args not in memo:
memo[args] = func(*args)
else:
print("Результат из кеша: ")
return memo[args]
return wrapper
Что здесь происходит: декоратор вручную управляет словарём кэша. Работает, но теряет метаданные исходной функции и не ограничивает размер кэша.
❌ Почему этот подход может быть не лучшим сейчас
- Имя и документация декорируемой функции теряются:
multiply.__name__станет'wrapper'. - Кэш неограничен: при большом количестве аргументов растёт потребление памяти.
- Нет поддержки ключевых аргументов и некортежируемых типов.
✅ Рекомендуемый современный вариант
from functools import lru_cache, wraps
@lru_cache(maxsize=128)
def multiply(a: int, b: int) -> int:
print(f"Вычисляем {a} * {b}: ")
return a * b
print(multiply(2, 3))
print(multiply(2, 3))
Что улучшилось: стандартный декоратор реализует LRU-политику, потокобезопасен и сохраняет метаданные функции.
- Не нужно писать сложную логику вытеснения.
- Можно быстро очистить кэш через
multiply.cache_clear(). - Статистика попаданий доступна через
multiply.cache_info().
📜 Как показано в исходном материале: базовое логирование
import logging
logging.basicConfig(
filename="call_log.log",
level=logging.INFO,
format="%(message)s",
encoding="utf-8",
)
def log_to_file(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
all_args = ", ".join(str(arg) for arg in args)
all_kwargs = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
logging.info(f"function: {func.__name__} | ...")
return result
return wrapper
✅ Современный вариант логирования
import logging
from functools import wraps
def log_to_file(func):
logger = logging.getLogger(func.__module__)
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
logger.info(
"function: %s | args: %s; kwargs: %s | return: %s",
func.__name__,
args or None,
kwargs or None,
result,
)
return result
return wrapper
Что улучшилось: используется именованный логгер, @wraps сохраняет метаданные, а формат строки передаётся через %-аргументы (ленивое форматирование, рекомендуемое самим модулем logging).
📜 Проверка типов из исходного материала
def accepts_types(*expected_types):
def decorator(func):
def wrapper(*args):
for arg, expected in zip(args, expected_types):
if not isinstance(arg, expected):
raise TypeError(f"Incorrect type for argument '{arg}': ...")
return func(*args)
return wrapper
return decorator
Ограничение: обёртка не принимает **kwargs, поэтому ключевые аргументы вызовут ошибку.
✅ Современный вариант проверки типов
from functools import wraps
from inspect import signature
def accepts_types(*expected_types):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
bound = signature(func).bind(*args, **kwargs)
bound.apply_defaults()
for param, value in bound.arguments.items():
if param in expected_by_name and not isinstance(value, expected_by_name[param]):
raise TypeError(f"Expected {expected_by_name[param]} for '{param}', got {type(value)}")
return func(*args, **kwargs)
return wrapper
return decorator
Альтернатива: для серьёзной проверки типов используйте аннотации и beartype / pydantic или статический анализатор mypy.
🕰️ Когда старый подход ещё можно встретить
Ручное кэширование встречается в legacy-коде и в учебных примерах, где важно понять внутреннее устройство декораторов. В продакшене предпочтительнее functools.lru_cache или специализированные библиотеки кэширования (Redis, memcached). Ручное логирование через print всё ещё встречается в скриптах, но для сервисов используйте модуль logging с именованными логгерами.