💻 Практические примеры

⚡ Минимальный рабочий пример

def limit_output(func):
    max_len = 20

    def wrapper():
        result = func()
        if len(result) > max_len:
            return result[:max_len - 3] + "..."
        return result

    return wrapper


@limit_output
def get_text():
    return "Это очень длинный текст, который нужно обрезать."


print(get_text())  # Это очень длинный...

Пример 1. Обрезка строки

Декоратор без параметров фиксирует максимальную длину внутри себя. Если результат функции длиннее лимита, он обрезается, а в конец добавляется троеточие.

def limit_output(func):
    max_len = 20

    def wrapper() -> str:
        result = func()
        if len(result) <= max_len:
            return result
        return result[:max_len - 3] + "..."

    return wrapper


@limit_output
def get_text() -> str:
    return "Это очень длинный текст, который нужно обрезать."


print(get_text())  # Это очень длинный...

Что происходит:

  1. Декоратор запоминает max_len = 20 в замыкании.
  2. При вызове get_text() обёртка получает строку длиной 49 символов.
  3. Так как 49 > 20, возвращаются первые 17 символов плюс ....

Пример 2. Параметризованная обрезка

Параметризованный декоратор позволяет задавать лимит при применении.

def limit_output(max_len: int):
    def decorator(func):
        def wrapper() -> str:
            result = func()
            if len(result) <= max_len:
                return result
            return result[:max_len - 3] + "..."
        return wrapper
    return decorator


@limit_output(26)
def get_text() -> str:
    return "Это очень длинный текст, который нужно обрезать."


print(get_text())  # Это очень длинный текст...

Пример 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. Кэш с ограничением размера

Когда кэш заполнен, самая старая запись удаляется. OrderedDict позволяет обновлять порядок при повторном обращении.

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))  # будет пересчитан

Пример 5. Логирование в файл

Декоратор записывает имя функции, аргументы и результат в файл call_log.log.

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()

Пример 6. Проверка типов

Декоратор проверяет позиционные аргументы по порядку. При несовпадении типов выбрасывает TypeError.

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(greet_by_name("Anna", 0))
except Exception as e:
    print(e)

Как запустить в VS Code

  1. Создайте файл practice_16.py в рабочей папке.
  2. Скопируйте код одного из примеров выше.
  3. Откройте терминал в VS Code (Ctrl + `).
  4. Убедитесь, что активировано виртуальное окружение: venv\Scripts\activate (Windows) или source venv/bin/activate (Mac/Linux).
  5. Запустите: python practice_16.py