ЗАМЕР
bench/decorators/wraps_signature.py
Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.
- Цитируется в статье
- /ru/interview/python/decorators
- Как запустить
python3.13 bench/decorators/cost.py python3.14 bench/decorators/cost.py python3.13 bench/decorators/wraps_signature.py
Запись прогона
Замеры для урока «Декораторы»
| Скрипт | Что меряет |
|---|---|
cost.py |
цену слоёв обёртки: 0, 1, 2 и 3 слоя *args, **kwargs с functools.wraps над одной и той же функцией |
wraps_signature.py |
что на самом деле показывает inspect.signature у обёртки с wraps и что меняет follow_wrapped=False |
python3.13 bench/decorators/cost.py
python3.14 bench/decorators/cost.py
python3.13 bench/decorators/wraps_signature.py
Каталог runs/ — записи прогонов
В runs/ лежит дословный вывод скриптов, без правок и без пересказа. Это не
украшение: на этих файлах стоят практические задачи урока. Ответ задачи —
строка из записи прогона, и сборка сайта сверяет одно с другим
(scripts/validate-practice.mjs): задача, чей ответ разошёлся с прогоном или
чей скрипт исчез, не собирается.
Прогоны сняты 30.08.2026 на CPython 3.13.7 (Clang 20.1.4) и 3.14.7
(Clang 22.1.3), в песочнице разработки. Числа урока взяты из ЭТИХ же прогонов
— таблица, заблуждение, визуализация WrapperCost и практическая задача
показывают одну четвёрку, а не три разных, как было до 30.08.
Абсолютные наносекунды всё равно принадлежат машине: на другой они будут другими. Переносится кратность, и практическая задача стоит именно на ней.
Перезаписывать запись прогона имеет смысл только вместе с проверкой задач: если после перезапуска ответ изменился, изменить нужно и задачу, а не файл.
Что здесь важно прочитать правильно
Меряется не «дорог ли декоратор», а во что обходится каждый следующий слой. Наносекунды на слой у каждой машины свои — в записи прогона это около 45 нс. Устойчиво здесь другое: рост линейный, и три слоя дают примерно семикратную цену вызова, то есть около семи восьмых времени уходит на обёртки, а не на работу. Именно эта кратность и переносится на другую машину.
Кратность устойчива только потому, что формы меряются вперемежку. Пока они мерились подряд, отношение трёх слоёв к голому вызову выходило 5,9 / 7,1 / 7,5 / 9,2 на четырёх запусках — просадка машины в окне одной формы целиком доставалась ей. После перехода на чередующиеся круги тот же замер даёт 7,1–7,7. Это правило теперь общее для всех скриптов с временем.
Общее правило замеров: время между версиями не сравнивается вообще. Сравнивается только измеренное внутри одного запуска одного интерпретатора. Числа 3.13.7 и 3.14.7 приводятся рядом как два независимых результата, а не как сравнение — цена слоя внутри каждого прогона своя, и именно она переносится на другую машину.
Чего этот замер не показывает
Времени применения декоратора. Оно тратится один раз при импорте и в горячем пути не участвует — а именно горячий путь и есть предмет урока. Порядок применения декораторов и связывание имени разобраны в уроке через дизассемблер, а не через время: там доказательство не наносекунды, а последовательность инструкций.
Скрипт
57 строк"""Что на самом деле видит `inspect.signature` у обёртки с `functools.wraps`.
ЗАЧЕМ ОТДЕЛЬНЫЙ СКРИПТ. Это код практической задачи урока про декораторы —
той, где читатель предсказывает вывод. Ответ задачи не сочиняется редакцией:
он берётся из записи прогона этого файла (`bench/decorators/runs/`), а сборка
сверяет, что напечатанное и заявленное совпадают дословно
(`scripts/validate-practice.mjs`). Поэтому здесь нет ничего лишнего: файл
должен печатать ровно то, что задача обещает читателю.
ЧТО ПРОВЕРЯЕТСЯ. Расхожая модель говорит: «`wraps` копирует сигнатуру на
обёртку». Это неверно. `wraps` копирует атрибуты (в том числе
`__annotations__`) и проставляет `__wrapped__`, а сигнатуру цели показывает
`inspect`, потому что по умолчанию идёт по этой цепочке. Отключите переход —
и обёртка снова окажется `(*a, **kw)`. Но возвращаемый тип при этом
останется: он приехал атрибутом, а не по цепочке.
Именно эта разница — между «обёртка приобрела сигнатуру» и «инструмент дошёл
до цели» — и отделяет знание механизма от пересказа.
Запускать: python3.11 / 3.12 / 3.13 / 3.14.
"""
import functools
import inspect
import sys
def deco(fn):
@functools.wraps(fn)
def wrapper(*a, **kw):
return fn(*a, **kw)
return wrapper
def target(x: int, y: str = "s") -> bool:
return True
three = deco(deco(deco(target)))
print("PY", sys.version.split()[0])
print()
print("вывод задачи:")
# Две строки идут подряд и без отступа намеренно: сборочная проверка ищет их
# в этом файле прогона дословно, вместе с переводом строки между ними.
print(inspect.signature(three))
print(inspect.signature(three, follow_wrapped=False))
print()
# Дальше — то, из чего этот вывод складывается. К ответу задачи не относится,
# но объясняет его, и потому лежит рядом, а не в тексте урока.
print("почему так:")
print(" __name__ обёртки: ", three.__name__)
print(" __wrapped__ ведёт к цели: ", inspect.unwrap(three) is target)
print(" аннотации скопированы атрибутом: ", target.__annotations__ == three.__annotations__)
print(" реальные параметры обёртки: ", three.__code__.co_varnames[: three.__code__.co_argcount + 2])