MEASUREMENT
bench/iterators/inlining.py
The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.
- Cited in
- /en/interview/python/iterators-and-generators
- How to run it
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v protocol.py; done
The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.
Record of the run
Замеры для урока «Итераторы и генераторы»
| скрипт | что показывает |
|---|---|
protocol.py |
iter(x) is x для списка и итератора, исчерпание, состояния генератора |
silent.py |
ошибка, которая не падает: два прохода по аргументу дают 0 вместо 19 |
memory_time.py |
пик памяти (tracemalloc) и время для списка против генератора на 1 000 000 и на 10 элементах |
traps.py |
PEP 479 (RuntimeError и __cause__), yield from против ручного цикла, in и len на генераторе |
versions.py |
запись __future__.generator_stop и возврат значения из close() |
inlining.py |
PEP 709 структурно: <listcomp>, MAKE_FUNCTION и глубина стека — без секундомера |
Запускать на всех версиях, которые есть:
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v protocol.py; done
Практика урока
practice.py — источник ответов двух практических задач урока, а runs/practice.txt —
дословная запись его прогона. Ответ задачи не сочиняется: сборка сверяет
заявленное с этой записью (scripts/validate-practice.mjs) и не проходит,
если они разошлись.
Прогон снят 30.08.2026 на CPython 3.13.7 (Clang 20.1.4). Абсолютные числа — этой машины; переносится кратность, и задача «во сколько раз» стоит именно на ней.
Кратность устойчива только потому, что формы меряются ВПЕРЕМЕЖКУ: в каждом круге меряются все, минимум для каждой берётся по кругам. Пока замеры шли подряд, просадка машины в окне одной формы целиком доставалась ей, и отношение гуляло в полтора раза от запуска к запуску (измерено на декораторах: 5,9 / 7,1 / 7,5 / 9,2). После перехода на чередование расхождение между прогонами не выходит за несколько процентов. Перезаписывать запись прогона имеет смысл только вместе с проверкой задачи: если после перезапуска ответ изменился, менять нужно задачу, а не файл.
Правило, из-за которого половина замеров сделана структурно
3.11 и 3.12 собраны GCC 13.3.0, а 3.13 и 3.14 — Clang 20.1.4. Сравнивать время между этими парами нельзя: разница сборок перекрывает разницу версий; это общее правило замеров, а не оговорка одного этого каталога.
Поэтому эффект PEP 709 в уроке показан не секундомером, а inlining.py:
наличием объекта кода <listcomp>, инструкции MAKE_FUNCTION и глубиной
стека внутри включения. Эти признаки от тулчейна не зависят вовсе.
Сравнение «генератор против списка» при этом остаётся честным всегда: оно делается внутри одного запуска одного интерпретатора.
Script
34 linesimport sys, dis, io, traceback
print("PY", sys.version.split()[0])
src = "def f(xs):\n return [x for x in xs]\n"
ns = {}
exec(compile(src, "<t>", "exec"), ns)
out = io.StringIO()
dis.dis(ns["f"], file=out)
text = out.getvalue()
print("списковое включение: отдельный объект кода <listcomp>:",
"<listcomp>" in text, "| MAKE_FUNCTION:", "MAKE_FUNCTION" in text)
src2 = "def g(xs):\n return (x for x in xs)\n"
ns2 = {}
exec(compile(src2, "<t>", "exec"), ns2)
out2 = io.StringIO()
dis.dis(ns2["g"], file=out2)
t2 = out2.getvalue()
print("генераторное выражение: отдельный объект кода <genexpr>:",
"<genexpr>" in t2, "| MAKE_FUNCTION:", "MAKE_FUNCTION" in t2)
# сколько кадров видно из включения
def where():
return len(traceback.extract_stack())
def via_listcomp():
return [where() for _ in range(1)][0]
def via_genexpr():
return list(where() for _ in range(1))[0]
def plain():
return where()
print("глубина стека: обычный вызов", plain(),
"| из спискового включения", via_listcomp(),
"| из генераторного выражения", via_genexpr())