Решение 1. Ограничение длины строки
def limit_output(func):
max_len = 20
def wrapper() -> str:
result = func()
if len(result) > max_len:
return result[:max_len - 3] + "..."
return result
return wrapper
@limit_output
def get_text() -> str:
return "Это очень длинный текст, который нужно обрезать."
print(get_text()) # Это очень длинный...
Логика: декоратор фиксирует лимит в замыкании. Обёртка проверяет длину результата и, если нужно, обрезает строку, оставляя место для троеточия.
Решение 2. Ограничение с указанием длины строки
def limit_output(max_len: int):
def decorator(func):
def wrapper() -> str:
result = func()
if len(result) > max_len:
return result[:max_len - 3] + "..."
return result
return wrapper
return decorator
@limit_output(26)
def get_text() -> str:
return "Это очень длинный текст, который нужно обрезать."
print(get_text()) # Это очень длинный текст...
Логика: внешняя функция limit_output получает параметр, средняя — декорируемую функцию, внутренняя — выполняет обрезку.
Решение 3. Кеширование результатов
def cache(func):
memo: dict[tuple, int] = {}
def wrapper(*args):
if args not in memo:
memo[args] = func(*args)
else:
print("Результат из кеша: ")
return memo[args]
return wrapper
@cache
def multiply(a: int, b: int) -> int:
print(f"Вычисляем {a} * {b}: ")
return a * b
print(multiply(2, 3))
print(multiply(2, 3))
print(multiply(4, 5))
print(multiply(2, 3))
Логика: ключом кэша служит кортеж позиционных аргументов. Если ключ уже есть, печатается сообщение и возвращается сохранённое значение.
Решение 4. Кеш с ограничением по размеру
from collections import OrderedDict
def cache(max_size: int = 5):
def decorator(func):
memo: OrderedDict[tuple, int] = OrderedDict()
def wrapper(*args):
if args in memo:
print("Результат из кеша: ")
memo.move_to_end(args)
else:
result = func(*args)
if len(memo) >= max_size:
memo.popitem(last=False)
memo[args] = result
return memo[args]
return wrapper
return decorator
@cache(max_size=2)
def multiply(a: int, b: int) -> int:
print(f"Вычисляем {a} * {b}: ")
return a * b
print(multiply(2, 3))
print(multiply(2, 3))
print(multiply(4, 5))
print(multiply(6, 7))
print(multiply(2, 3)) # будет пересчитан
Логика: OrderedDict хранит порядок вставки. При повторном доступе элемент переносится в конец через move_to_end. Если кэш переполнен, удаляется самый старый элемент через popitem(last=False).
Решение 5. Журнал вызовов функции
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__} | "
f"args: {all_args if all_args else None}; "
f"kwargs: {all_kwargs if all_kwargs else None} | "
f"return: {result}"
)
return result
return wrapper
@log_to_file
def add(a: int, b: int) -> int:
return a + b
@log_to_file
def greet_with_name(name: str, punctuation: str = "!") -> str:
return f"Hello, {name}{punctuation}"
@log_to_file
def greet() -> str:
return "Hello"
add(5, 3)
greet_with_name("Alice", punctuation=".")
greet()
Логика: обёртка вызывает исходную функцию, формирует строку с аргументами и результатом и записывает её через logging.info. Аргументы None используются, когда позиционных или ключевых аргументов нет.
Решение 6. Проверка типов аргументов по порядку
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}': "
f"expected - {expected}, received - {type(arg)}"
)
return func(*args)
return wrapper
return decorator
@accepts_types(int, int)
def add(a: int, b: int) -> int:
return a + b
@accepts_types(str)
def greet(name: str) -> str:
return f"Hello, {name}!"
@accepts_types(str, str)
def greet_by_name(name: str, punctuation: str = "!") -> str:
return f"Hello, {name}{punctuation}"
try:
print(add(5, 3))
print(greet_by_name("Anna"))
# print(add("one", 3)) # ошибка
print(greet_by_name("Anna", 0)) # ошибка
except Exception as e:
print(e)
Логика: zip(args, expected_types) сопоставляет каждый переданный аргумент с ожидаемым типом по порядку. При несовпадении выбрасывается TypeError с подробным сообщением.
mypy вместо ручных декораторов.