ЗАМЕР
bench/imports/practice.py
Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.
- Цитируется в статье
- /ru/interview/python/modules-and-imports
- Как запустить
python3.11 bench/imports/basics.py python3.12 bench/imports/basics.py python3.13 bench/imports/basics.py python3.14 bench/imports/basics.py python3.13 bench/imports/cost.py python3.14 bench/imports/cost.py python3.11 bench/imports/practice.py python3.12 bench/imports/practice.py python3.13 bench/imports/practice.py python3.14 bench/imports/practice.py
Запись прогона
Замеры для урока «Модули и импорт»
| Скрипт | Что делает |
|---|---|
basics.py |
наблюдения без времени: модуль выполняется один раз, from m import x копирует ссылку, __name__ при запуске и при импорте, циклический импорт, свой random.py рядом со скриптом, пакет и __init__.py; раздел 7 — встроенный модуль, пакет без __init__.py, границы кеша sys.modules, полный пример python -m пакет.модуль |
cost.py |
цена повторного импорта (import json внутри функции) и первого импорта json, decimal, asyncio в свежем интерпретаторе |
practice.py |
источник ответов двух практических задач урока |
python3.11 bench/imports/basics.py
python3.12 bench/imports/basics.py
python3.13 bench/imports/basics.py
python3.14 bench/imports/basics.py
python3.13 bench/imports/cost.py
python3.14 bench/imports/cost.py
python3.11 bench/imports/practice.py
python3.12 bench/imports/practice.py
python3.13 bench/imports/practice.py
python3.14 bench/imports/practice.py
Записи в runs/: без суффикса — CPython 3.13.7, остальные — с суффиксом
версии (3.11.15, 3.12.3, 3.14.7). Каждый сюжет basics.py запускается в
отдельном интерпретаторе во временной папке; путь к ней в выводе заменён на
<папка>. Все прогоны — Linux x86_64, одна машина (2 ядра); сверено
14.10.2026. Числа cost.py — наблюдения этой конфигурации: на другой машине
и версии меняются и числа, и их отношения, — мерить у себя. Раздел 7
basics.py добавлен по внешнему аудиту урока 14.10.2026; разделы 1–6 при
перезапуске не изменились.
Что получилось
Один раз. Три import noisy подряд и ещё тысяча в цикле выполнили тело
модуля один раз; importlib.reload выполнил его снова — в том же объекте
модуля, а имя, взятое через from до перезагрузки, осталось старым
объектом. Модуль лежит в sys.modules уже во время выполнения своего тела,
и import noisy as again даёт тот же объект.
from копирует ссылку. После from settings import DEBUG присваивание
settings.DEBUG = True не меняет DEBUG у импортирующего — он остался
False. Список ITEMS общий: append через модуль виден, ITEMS is settings.ITEMS — True. Отсюда подмена в тестах: settings.DEBUG = True до
первого import report (где from settings import DEBUG) report видит,
после — нет; mock.patch('settings.DEBUG') до report не доходит,
mock.patch('report.DEBUG') — доходит.
__name__. python tool.py и python -m tool печатают '__main__' и
выполняют блок if __name__ == "__main__"; import tool печатает 'tool', и
блок не выполняется. Скрипт, который импортирует сам себя по имени файла,
выполняет тело дважды — как '__main__' и как 'selfimp', — и это два
разных объекта модуля.
Циклический импорт (модули в пакете app/, на всех четырёх версиях
одинаково):
from app.b import Y↔from app.a import X—ImportError: cannot import name 'X' from partially initialized module 'app.a' (most likely due to a circular import);import app.aиapp.a.Xна уровне модуляb—AttributeError: cannot access submodule 'a' of module 'app' (most likely due to a circular import);import app.aиapp.a.Xвнутри функции — работает,app.b.y() -> 2.
Те же два файла прямо в папке запуска: на 3.11 и 3.12 текст ошибки тот же
(«partially initialized module 'a'»), а на 3.13 и 3.14 подсказка о цикле
заменена подсказкой о переименовании: cannot import name 'X' from 'a' (consider renaming '<папка>/a.py' if it has the same name as a library you intended to import).
Свой random.py. Рядом со скриптом он заслоняет стандартный модуль:
выполняется он, и random.randint — AttributeError. На 3.13 и 3.14 к
тексту ошибки добавлена подсказка: (consider renaming '<папка>/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module); на 3.11 и 3.12 её нет.
Пакет. import pkg.sub сначала выполняет pkg/__init__.py, потом
pkg/sub.py. import pkg подмодуль не загружает: hasattr(pkg, "sub") —
False, pkg.sub — AttributeError. json/__init__.py свои подмодули
импортирует сам: после import json в sys.modules есть json.decoder и
json.encoder.
Раздел 7. sys встроен: sys.__spec__.origin — 'built-in', __file__
нет. _json в сборках 3.11.15 и 3.12.3 — модуль-расширение .so, в 3.13.7 и
3.14.7 — встроен (свойство сборки, единственная строка раздела, которая
различается по версиям). Папка nspkg/ без __init__.py импортируется:
nspkg.__file__ — None, __path__ — список из одной папки. После
del sys.modules['noisy'] следующий import выполнил тело снова и дал новый
объект. Модуль, упавший при первой загрузке, в sys.modules не остался, и
повторный import выполнил его снова. Отдельный процесс через subprocess
выполнил тело заново. python -m shop.cli: __name__ — '__main__',
__package__ — 'shop', sys.path[0] — папка запуска, относительный
from . import PRICE работает; python shop/cli.py — ImportError: attempted relative import with no known parent package.
Цена (медианы, 3.13.7): import json внутри функции стоит 84,3 нс сверх
пустого вызова (3.14.7 — 102,3). Первый импорт в свежем интерпретаторе:
json — 9 128,1 мкс, decimal — 3 595,0, asyncio — 54 409,2. Первый
import json дороже повторного в 108 256 раз (3.14.7 — в 90 965).
Скрипт
50 строк"""Практика урока «Модули и импорт»: источник ответов двух задач.
Ответ задачи не сочиняется редакцией — он берётся из записи прогона этого
скрипта (`runs/practice.txt`), а сборка сверяет одно с другим
(`scripts/validate-practice.mjs`).
ЧТО ПРОВЕРЯЮТ ЗАДАЧИ. Первая — что `from config import DEBUG` копирует
ссылку на значение в момент импорта: новое присваивание `config.DEBUG`
это имя не меняет. Вторая — сколько раз выполняется тело модуля, если
импортировать его тысячу раз и дважды перезагрузить.
Модули задач (`config.py`, `counter.py`) скрипт пишет во временную папку и
добавляет её в `sys.path` — так задача выглядит как обычный проект из
нескольких файлов.
Запускать: python3.11 / 3.12 / 3.13 / 3.14.
"""
import importlib
import os
import sys
import tempfile
print("PY", sys.version.split()[0])
print()
tmp = tempfile.mkdtemp()
with open(os.path.join(tmp, "config.py"), "w", encoding="utf-8") as f:
f.write("DEBUG = False\n")
with open(os.path.join(tmp, "counter.py"), "w", encoding="utf-8") as f:
f.write("import builtins\nbuiltins.RUNS = getattr(builtins, 'RUNS', 0) + 1\n")
sys.path.insert(0, tmp)
sys.dont_write_bytecode = True
print("вывод задачи:")
import config
from config import DEBUG
config.DEBUG = True
print(DEBUG, config.DEBUG)
print()
print("оценка:")
import builtins # noqa: E402
for _ in range(1000):
import counter # noqa: E402,F401
importlib.reload(counter)
importlib.reload(counter)
print("тело модуля counter выполнилось раз:", builtins.RUNS)