ЗАМЕР
bench/lambda/silent.py
Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.
- Цитируется в статье
- /ru/interview/python/lambda
- Как запустить
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v late_binding.py; done
Запись прогона
Замеры для урока «lambda»
| скрипт | что показывает |
|---|---|
identity.py |
тип, байт-код и __name__ у lambda и def; что в lambda не компилируется |
late_binding.py |
[lambda: i for i in range(3)] даёт [2, 2, 2]; одна ячейка на три функции; чем включение отличается от for-цикла |
silent.py |
ошибка, которая не падает: три проверки с одним порогом дают 3 вместо 6 |
cost.py |
lambda против def при вызове и в key=; operator.itemgetter против обоих |
Запускать на всех версиях, которые есть:
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v late_binding.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.7 и 3.14.7 — Clang 20.1.4, но и эти две сборки различаются между собой,
причём ровно тем флагом (--with-tail-call-interp), которому «Что нового в
3.14» приписывает «a geometric mean of 3-5% faster».
В cost.py все сравнения делаются внутри одного запуска одного
интерпретатора: lambda против def, и оба против itemgetter. Такое
сравнение честно всегда и от тулчейна не зависит. Числа для 3.13.7 и
3.14.7 приводятся рядом как два независимых результата, а не как
сравнение.
Отдельно про то, чего в cost.py НЕТ: там не измеряется sorted(DATA) без
key. Он сравнивал бы кортежи по первому элементу, то есть по строке, а три
измеряемые формы сортируют по числу — это разная работа, и приписывать
разницу способу извлечения ключа было бы подлогом.
identity.py, late_binding.py и silent.py от тулчейна не зависят вовсе:
они смотрят на типы, имена, содержимое ячеек замыкания и результат вычисления.
Разброс между запусками
cost.py — лучшее из семи прогонов. Повторные запуски дают разброс около
±3 %, и он важен для чтения результата: разница между key=lambda и key=def
в него укладывается (в повторных прогонах порядок этих двух даже меняется
местами), а разница с itemgetter — нет, она устойчиво в полтора раза.
Поэтому в уроке сказано «неотличимо» про первую пару и названа кратность для
второй.
Скрипт
32 строк"""Ошибка, которая не падает: набор проверок, собранный в цикле.
Как и в трёх предыдущих уроках, интересна не та ошибка, что роняет программу,
а та, что молча возвращает неверный ответ.
"""
import sys
print("PY", sys.version.split()[0])
VALUES = [5, 50, 500, 5000]
THRESHOLDS = [10, 100, 1000]
# НЕВЕРНО: три проверки, но порог у всех один — последний.
broken = [lambda v: v > t for t in THRESHOLDS]
# ВЕРНО: порог зафиксирован аргументом по умолчанию.
correct = [lambda v, t=t: v > t for t in THRESHOLDS]
def report(checks, label):
counts = [sum(1 for v in VALUES if check(v)) for check in checks]
print(f" {label}: прошло порогов {THRESHOLDS} -> {counts}, всего {sum(counts)}")
return sum(counts)
print("1) сколько значений проходит каждый порог")
bad = report(broken, "неверно ")
good = report(correct, "верно ")
print(f" ожидалось {good}, получено {bad}; исключений не было")
# 2. Признак, по которому это видно в отладчике: у сломанных функций одна
# ячейка замыкания на всех, у правильных замыкания нет вовсе.
print("2) ячеек замыкания у сломанных:", len({id(f.__closure__[0]) for f in broken}))
print(" замыкание у правильных:", correct[0].__closure__)