UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

Декораторы: функции, оборачивающие функции

core написать простой декоратор и применить его для сквозной функциональности (например, логирования вызовов) вместо копипасты кода в каждую функцию

В C сквозная функциональность — логирование входа/выхода, замер времени, проверка прав — решается обычно одним из двух способов:

  1. Макрос препроцессора — текстовая подстановка до компиляции: мощно, но небезопасно и плохо отлаживается.
  2. Функция-обёртка — её нужно писать вручную, синхронно поддерживать при изменении сигнатуры оригинала и не забывать вызывать вместо оригинала.
void original_function(int arg) { /* ... */ }

void logged_function(int arg) {
    printf("Calling with arg: %d\n", arg);
    original_function(arg);
    printf("Function finished.\n");
}

logged_function(42);  // легко по ошибке вызвать original_function

В Python функции — объекты первого класса: их можно передавать как аргументы, возвращать из других функций, хранить в переменных. Декоратор — это функция, которая принимает функцию и возвращает другую функцию, обычно оборачивающую первую дополнительным поведением. Синтаксис @decorator над def — чистый синтаксический сахар:

@log_calls
def compute(x, y):
    return x + y

# полностью эквивалентно:
def compute(x, y):
    return x + y
compute = log_calls(compute)

Никакой магии: декоратор вызывается один раз, в момент определения функции, и имя compute после этого связано с тем объектом, который вернул log_calls.

import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Вызов {func.__name__} с {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} вернула {result}")
        return result
    return wrapper

Здесь wrapper — замыкание, захватившее func (см. предыдущий урок про LEGB). *args, **kwargs позволяют оборачивать функцию любой сигнатуры без переписывания декоратора под каждый набор параметров — то, что в C потребовало бы либо variadic-функций с va_list и всей их хрупкостью, либо отдельного макроса под каждую сигнатуру.

Важная деталь: без functools.wraps обёрнутая функция «представляется» как wrapper — её __name__ и __doc__ берутся у обёртки. functools.wraps копирует __name__, __doc__ и другие метаданные с оригинала на wrapper; без этого интроспекция (help(), логи, отладка) сбивает с толку.

Декораторы — идиоматичный способ реализовать сквозные задачи в стиле DRY. Стандартная библиотека активно их использует: functools.lru_cache для кэширования, staticmethod и classmethod при описании методов классов.

graph TD
    A["def compute(...) с @log_calls"] --> B["вызывается log_calls(compute)"]
    B --> C["создаётся wrapper (замыкание над оригиналом)"]
    C --> D["оригинальная compute доступна как func внутри wrapper"]
    B --> E["имя compute теперь указывает на wrapper"]
В момент определения функции декоратор вызывается один раз, и имя функции связывается с wrapper.
sequenceDiagram
    participant Caller as Вызывающий код
    participant Wrapper as wrapper (декоратор)
    participant Orig as compute (оригинал)
    Caller->>Wrapper: compute(x, y)
    Wrapper->>Wrapper: логирование / проверка
    Wrapper->>Orig: func(x, y)
    Orig-->>Wrapper: результат
    Wrapper-->>Caller: результат
Декоратор перехватывает вызов, добавляет поведение до и после, затем делегирует оригинальной функции.

Разобранный пример

Допустим, пять функций нужно защитить проверкой прав доступа. В C-стиле проверка дублируется в начале каждой:

int compute(int x) {
    if (!check_permission()) return -1;
    /* основная логика */
}

И это дублирование пришлось бы повторить в каждой из пяти функций, синхронно поддерживая при изменении логики проверки.

В Python декоратор выносит проверку один раз:

import functools

def require_permission(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if not check_permission():
            raise PermissionError(f"Нет доступа к {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@require_permission
def compute(x):
    return x * 2

@require_permission
def delete_record(record_id):
    ...

Логика проверки существует в одном месте, применяется декларативно через @, а изменение правил не требует трогать код compute или delete_record — в отличие от явного вызова вспомогательной функции в каждом месте (риск забыть) или макроса, теряющего читаемость при отладке.

Попробуй сейчас

Напишите декоратор @timed, который замеряет время выполнения функции через time.perf_counter() и печатает "{имя функции} выполнилась за {время} сек". Не забудьте functools.wraps. Примените его к функции с искусственной задержкой (time.sleep(0.1)) и убедитесь, что в выводе — настоящее имя функции, а не wrapper.

Дополнительно: напишите декоратор check_positive, который проверяет, что все аргументы из *args и **kwargs.values() — числа больше нуля, и иначе бросает ValueError. Примените к calculate_area(width, height).

Получилось, если…

Вы разобрались, если можете написать декоратор с нуля (принимающий функцию, возвращающий wrapper с *args, **kwargs), объяснить назначение functools.wraps и назвать разницу между декоратором и C-макросом: декоратор — обычный код, выполняемый при определении функции и работающий с объектами-функциями, а не текстовая подстановка препроцессора до компиляции.

Вывод

Декоратор — функция, принимающая функцию и возвращающая функцию; @decorator — сахар для f = decorator(f), выполняемый один раз при определении. *args, **kwargs и functools.wraps — обязательный минимум для универсального и «прозрачного» декоратора.

AI-generated · source-grounded review

🛡 Fact-checked: 0 risky claims verified · 0 removed · confidence: high · figures: 2
[unconfirmed by second model] @decorator эквивалентен f = decorator(f) и выполняется один раз при определении функции
Обе версии согласны; функции — объекты первого класса согласно базе знаний
Second model: База знаний не обсуждает декораторы или эквивалентность @decorator присваиванию.
[unconfirmed by second model] functools.wraps копирует __name__, __doc__ и другие метаданные на wrapper
Стандартное поведение functools; база знаний упоминает модуль functools как часть стандартной библиотеки
Second model: База знаний не упоминает functools.wraps или копирование метаданных.
[unconfirmed by second model] Стандартная библиотека использует декораторы: functools.lru_cache, staticmethod, classmethod
Существующие элементы стандартной библиотеки/языка; не противоречит базе знаний
Second model: База знаний не перечисляет стандартные декораторы вроде lru_cache или staticmethod.

A second model re-checked this lesson's claims and corroborated 0 of 3 , and could not confirm 3 either way. This is incomplete corroboration, not a disagreement.

Key concepts: декоратор functions as first-class objects wrapper
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course

Check yourself

1. Что делает синтаксис `@log_calls` над `def compute(): ...`?
2. Зачем нужен functools.wraps внутри декоратора?
3. Почему декораторы возможны в Python без макросов препроцессора?
On your own course, these are marked as you answer