При декорировании функции с именованными аргументами обёртка принимает только позиционные аргументы: какое изменение контракта получает декорированная функция?
Декорированная функция перестаёт принимать вызовы с именованными аргументами, даже если исходная функция их поддерживала. Такой декоратор изменяет внешний контракт функции: вызов с keyword-аргументом завершится ошибкой уже на уровне обёртки.
Декораторы в Python предназначены для добавления общего поведения — например, журналирования, проверки доступа или измерения времени — без изменения тела исходной функции. Основой служит замена исходного объекта функцией-обёрткой, поэтому корректность интерфейса обёртки становится частью корректности декоратора.
Синтаксис декораторов делает такую замену компактной, но не скрывает её последствий: после декорирования имя обычно указывает на обёртку, а не непосредственно на исходную функцию.
Если обёртка объявлена только с параметром *args, она умеет принимать произвольное число позиционных аргументов, но не принимает аргументы, переданные по имени. Поэтому исходная функция может поддерживать вызов вроде func(10, limit=5), а после декорирования такой вызов завершится TypeError.
Это особенно опасно для публичных функций, библиотечного кода и callback-интерфейсов: ошибка проявляется не при создании декоратора, а только в конкретном варианте вызова. Пользователь может ошибочно решить, что проблема находится в исходной функции.
Универсальная обёртка обычно принимает и *args, и **kwargs, а затем передаёт оба набора исходной функции. *args содержит позиционные аргументы, а **kwargs — именованные; потеря любого из них сужает поддерживаемый интерфейс.
В этом примере вызов доходит до wrapper, но параметр limit попадает в kwargs, которого обёртка не принимает. Возникает TypeError до выполнения total.
Исправленный вариант должен использовать def wrapper(*args, **kwargs) и вызывать func(*args, **kwargs). При этом functools.wraps решает другую задачу: сохраняет метаданные исходной функции и помогает инструментам интроспекции, но не добавляет обёртке поддержку именованных аргументов автоматически.
Если декоратор намеренно ограничивает интерфейс, это должно быть явно задокументировано. В общем случае предпочтительно сохранять вызываемый контракт исходной функции, иначе декоратор нарушает принцип прозрачного оборачивания.
В веб-сервисе декоратор аудита оборачивает обработчики запросов. Один обработчик принимает обязательный keyword-only параметр request_id, а инфраструктура вызывает его по имени. Обёртка с одним *args успешно работает в тестах, где параметры передаются позиционно, но падает в рабочем окружении с TypeError.
Рассматривались два варианта. Можно было изменить все места вызова на позиционные аргументы, но это сделало бы API менее выразительным и создало бы лишнюю связанность с реализацией декоратора. Можно было добавить в обёртку **kwargs и передавать их дальше; этот вариант сохраняет исходный контракт и требует минимальных изменений.
Выбран второй вариант. После этого декоратор стал прозрачно поддерживать оба способа передачи аргументов, а functools.wraps дополнительно сохранил имя, документацию и ссылку __wrapped__ для инструментов анализа.
**kwargs, чтобы полностью сохранить поведение исходной функции?Нет. Это сохраняет передачу именованных аргументов во время вызова, но не гарантирует сохранение сигнатуры для inspect.signature, документации, IDE и генераторов схем. Для метаданных обычно применяют functools.wraps; при динамически изменяемом интерфейсе может потребоваться явная настройка __signature__.
Python сначала пытается связать фактические аргументы с параметрами вызываемого объекта, которым после декорирования является wrapper. Если у неё нет **kwargs, именованный аргумент отвергается до выполнения тела обёртки и до вызова исходной функции. Поэтому трассировка указывает на внешний слой, хотя исходная функция такой вызов поддерживала.
kwargs исходной функции?Для прозрачного декоратора — обычно да, но не для каждого специализированного адаптера. Если декоратор сознательно извлекает или преобразует параметры, он может изменить контракт, однако это должно быть частью его назначения и документации. Без явного намерения удаление или переименование аргументов является скрытым несовместимым изменением API.