Модули и импорт: модуль выполняется один раз, а from копирует ссылку
Вопросы про импорт кажутся служебными, пока не спросят, что именно выполняется при import. Прогон отвечает: тело модуля выполнилось один раз на тысячу и три импорта — и снова, когда запись удалили из sys.modules; после from settings import DEBUG присваивание settings.DEBUG = True новое значение не показало; циклический импорт упал на from и заработал, когда обращение к модулю ушло внутрь функции; а свой файл random.py рядом со скриптом заслонил стандартный модуль.
Полное техническое изложение
TL;DR
Модуль — обычно файл .py, а import делает две вещи: находит модуль,
выполняя его тело при первой успешной загрузке, и связывает имя. Загруженный
модуль хранится в словаре sys.modules текущего интерпретатора, и повторный
успешный import под тем же полным именем берёт оттуда готовый объект. В
прогоне три import noisy подряд и ещё тысяча в цикле выполнили тело модуля
ровно один раз; после удаления записи из sys.modules следующий import
выполнил его снова.
Отсюда главное следствие: from settings import DEBUG — это присваивание
DEBUG = settings.DEBUG в момент импорта, а не живая связь с модулем. После
settings.DEBUG = True импортированное имя DEBUG осталось False. Список,
импортированный так же, — общий: append через модуль виден, потому что
объект один.
Дальше — то, что отличает понимание от пересказа. Циклический импорт
падает не всегда: from app.a import X из недоимпортированного модуля даёт
ImportError, а import app.a с обращением app.a.X внутри функции
работает. Свой файл random.py рядом со скриптом заслоняет стандартный
модуль — начиная с 3.13 ошибка об этом прямо подсказывает. А повторный
import внутри функции в замере на CPython 3.13.7 стоил 84,3 нс сверх пустого
вызова, тогда как первый import json в свежем интерпретаторе — 9,1 мс.
Прогоны — CPython 3.11.15, 3.12.3, 3.13.7 и 3.14.7 на Linux x86_64; сверено
14.10.2026. В вышедшем к этой дате Python 3.15 появились явные ленивые
импорты (PEP 810); урок описывает обычный, немедленный import и на 3.15 не
запускался.
- как запустить скрипт командой
python файл.py; - что такое функция и глобальная переменная модуля;
- что словарь хранит значения по ключам — урок «Встроенные коллекции»;
- для примера с подменой в тестах — что делает
mock.patch: подробно — в уроке «Тестирование»; здесь достаточно «временно подменяет имя на время блокаwith».
sys.modules,importlib.reload,from … import;__name__ == "__main__",python -m, циклический импорт;- пакеты и
__init__.py, цена первого и повторного импорта.
База: модуль — обычно файл, а import — две операции
Типичный модуль — файл .py. Его имена — функции, классы, переменные
верхнего уровня — становятся атрибутами объекта-модуля. Полные файлы —
settings.py и main.py в одной папке, запуск — python3 main.py:
# settings.py
DEBUG = False
# main.py
import settings
print(settings.DEBUG) # FalseФайл .py — не единственный вид модуля: sys встроен в интерпретатор и
файла не имеет, а модуль-расширение на C — файл .so (в Windows — .pyd).
Пакет — модуль, внутри которого лежат другие модули; обычный пакет —
папка с файлом __init__.py, но и папка без него импортируется как пакет
(раздел «Глубже»). Дальше в уроке речь об обычном случае — файлах .py и
папках с __init__.py.
Ответ уровня Junior на «что делает import»: находит модуль, при первой
загрузке выполняет его и делает его имена доступными через имя модуля;
from m import x достаёт одно имя напрямую. Это верно, и за этим ответом идут уточнения:
выполняется ли модуль при каждом импорте, чем from отличается не только
синтаксисом и откуда берутся ошибки циклического импорта. Документация
описывает import как два шага:
The basic import statement (no "from" clause) is executed in two steps:
- find a module, loading and initializing it if necessary
- define a name or names in the local namespace for the scope where the
"import" statement occurs.
Простой оператор import, без from, выполняется в два шага: 1) найти модуль, при необходимости загрузив и инициализировав его; 2) определить имя или имена в локальном пространстве имён той области, где стоит оператор import.
Ключевые слова здесь — «при необходимости». Механизмы ниже — о том, когда эта необходимость есть и что из этого следует.
Как читать блоки кода: блок с пометкой «полный файл» копируется как есть;
блок без языка — дословный вывод прогона из bench/imports/runs/; строки,
начинающиеся с отступа без заголовка раздела, — фрагмент этого вывода.
Механизм 1: модуль выполняется один раз — sys.modules
Модуль noisy печатает строку, когда выполняется его тело, — и заодно
проверяет, есть ли он уже в sys.modules. Скрипт импортирует его трижды,
потом ещё тысячу раз в цикле, потом перезагружает:
1) модуль выполняется один раз
[тело noisy выполняется; noisy уже в sys.modules: True ]
после трёх import:
noisy в sys.modules -> True
again is noisy -> True
sys.modules['noisy'] is noisy -> True
ещё 1000 import в цикле — тело не выполнялось
[тело noisy выполняется; noisy уже в sys.modules: True ]
после reload — тело выполнилось снова
reload вернул тот же объект модуля -> True
имя из from до reload is noisy.ITEMS -> False
Первый import создал объект модуля, положил его в sys.modules —
словарь загруженных модулей, где ключ — имя модуля, — и только потом
выполнил тело: первая строка показывает, что модуль лежит там ещё во время
выполнения. На этом порядке держится поведение циклического импорта
(механизм 4). Каждый следующий import находит модуль в словаре и только
связывает имя: import noisy as again дал тот же самый объект.
importlib.reload выполняет тело заново — в том же объекте модуля. Но
имена, взятые через from до перезагрузки, указывают на старые объекты:
список ITEMS, импортированный раньше, — уже не noisy.ITEMS.
reload — не единственный способ выполнить модуль снова. Кеш — обычный
словарь одного интерпретатора, и у правила есть границы. Раздел 7 того же
прогона (добавлен 14.10.2026), фрагмент:
del sys.modules['noisy'] и снова import noisy:
[тело noisy выполняется]
[тело noisy выполняется]
новый объект модуля -> True
первая загрузка упала — import пробует снова:
[тело flaky выполняется]
RuntimeError: первая загрузка упала
'flaky' in sys.modules -> False
[тело flaky выполняется]
'flaky' in sys.modules -> True
другой интерпретатор — свой sys.modules:
[тело noisy выполняется]
[тело noisy выполняется]
После del sys.modules['noisy'] следующий import выполнил тело снова и дал
новый объект модуля. Модуль flaky, упавший при первой загрузке, в кеше не
остался, и второй import выполнил его ещё раз — уже успешно. А отдельный
процесс Python (здесь — запущенный через subprocess) выполнил тело заново:
у него свой sys.modules. Точное правило: повторный успешный import под
тем же полным именем в том же интерпретаторе берёт существующую запись
sys.modules.
Из этого правила вытекают три практических следствия:
- Код верхнего уровня модуля выполняется при первом импорте. Подключение
к базе, чтение файла,
printна уровне модуля сработают в момент, когда модуль впервые кто-то импортирует, — не раньше и не повторно. - Модуль — естественный синглтон в пределах интерпретатора. Объект,
созданный на уровне модуля, один на процесс: все импортирующие получают
один и тот же модуль. Точнее — один на имя в
sys.modules: исключение разобрано в механизме 3, а удаление записи из кеша создаёт новый. importвнутри функции не выполняет модуль заново. После первого раза это поиск в словаре — цена разобрана в разделе «Глубже».
Механизм 2: from m import x копирует ссылку
Для формы from документация описывает, что происходит с каждым именем:
a reference to that value is stored in the local namespace
Ссылка на это значение сохраняется в локальном пространстве имён.
Ссылка на значение — не на имя в модуле. from settings import DEBUG
работает как DEBUG = settings.DEBUG, выполненное один раз. Модуль
settings объявляет DEBUG = False и ITEMS = []:
2) from m import x копирует ссылку, а не связывает имена
settings.DEBUG -> True
DEBUG -> False
settings.ITEMS -> ['x']
ITEMS -> ['x']
ITEMS is settings.ITEMS -> True
Присваивание settings.DEBUG = True связало имя DEBUG в модуле с новым
объектом, а имя DEBUG у импортирующего по-прежнему указывает на старый —
False. Со списком иначе: append не связывает имя заново, а меняет сам
объект, и он у модуля и у импортирующего один. Это то же правило, что
«присваивание связывает имя, а изменение объекта видно всем, кто на него
ссылается», только между модулями.
Практическое следствие — подмена в тестах. Модуль report делает
from settings import DEBUG, а его функция show() возвращает DEBUG:
подмена settings.DEBUG = True до первого import report:
report.show() -> True
подмена settings.DEBUG = True после import report:
report.show() -> False
mock.patch после import report:
mock.patch('settings.DEBUG') -> report.show() = False
mock.patch('report.DEBUG') -> report.show() = True
Подмена до первого импорта report срабатывает — from скопирует уже
подменённое значение. После импорта она до report не доходит: у него своё
имя. Поэтому mock.patch ставят там, где имя ищется, — report.DEBUG, — или
в report пишут import settings и читают settings.DEBUG в момент
использования.
Механизм 3: __name__ == "__main__"
The name used to uniquely identify the module in the import system. For a
directly executed module, this will be set to "main".
Имя, по которому модуль однозначно опознаётся системой импорта. У модуля, запущенного напрямую, оно равно __main__.
Один и тот же файл tool.py — запущенный как скрипт, импортированный и
запущенный через -m:
3) __name__: один файл — запуск и импорт
python tool.py:
__name__ = '__main__'
блок if __name__ == '__main__' выполнен
python -c 'import tool':
__name__ = 'tool'
python -m tool:
__name__ = '__main__'
блок if __name__ == '__main__' выполнен
Зачем нужна проверка, видно из механизма 1: при импорте тело модуля
выполняется целиком. Код, который должен работать только при запуске файла —
разбор аргументов командной строки, запуск сервера, — прячут под
if __name__ == "__main__":. Без неё import tool из теста или другого
модуля запустил бы программу.
Отсюда же исключение из правила «модуль выполняется один раз». Запущенный
файл лежит в sys.modules под именем __main__, и если что-то импортирует
его по имени файла, это второй, отдельный модуль:
python selfimp.py — скрипт импортирует сам себя:
тело выполняется, __name__ = '__main__'
тело выполняется, __name__ = 'selfimp'
selfimp is sys.modules['__main__'] -> False
Тело выполнилось дважды, и объекты верхнего уровня у двух копий разные. Так бывает, когда модуль приложения одновременно и точка входа, и импортируется другими модулями.
Модуль пакета запускают через -m. Полный пример из двух файлов в папке
shop/; команды выполняются из папки, в которой лежит shop/.
# shop/__init__.py — полный файл
PRICE = 10# shop/cli.py — полный файл
import sys
from . import PRICE
print("__name__ =", repr(__name__))
print("__package__ =", repr(__package__))
print("sys.path[0] =", sys.path[0])
print("PRICE =", PRICE)Вывод python3 -m shop.cli и python3 shop/cli.py — раздел 7 прогона; от
второй команды показана последняя строка трассировки:
python -m shop.cli:
__name__ = '__main__'
__package__ = 'shop'
sys.path[0] = <папка>
PRICE = 10
python shop/cli.py:
ImportError: attempted relative import with no known parent package
-m shop.cli ищет модуль по полному имени, как import: сначала пакет
shop, потом его модуль cli. Поэтому __package__ равен 'shop', и
относительный импорт from . import PRICE знает, от чего отсчитывать точку.
Первым в пути поиска стоит текущая папка (sys.path[0] — та, где лежит
shop/). Тот же файл, запущенный по пути, — просто скрипт: пакета у него
нет, и относительный импорт падает. __name__ в обоих случаях —
'__main__'.
Механизм 4: циклический импорт — где он падает, а где нет
Модуль app.a импортирует app.b, а app.b — app.a. Пока app.a
выполняется, он уже лежит в sys.modules, но выполнен только до строки
импорта: имени X в нём ещё нет. Исход зависит от того, когда app.b
обращается к X:
4) циклический импорт
app/a: from app.b import Y; app/b: from app.a import X — import app.a:
ImportError: cannot import name 'X' from partially initialized module 'app.a' (most likely due to a circular import) (<папка>/app/a.py)
app/a: import app.b; app/b: import app.a и app.a.X на уровне модуля:
AttributeError: cannot access submodule 'a' of module 'app' (most likely due to a circular import)
app/a: import app.b; app/b: import app.a и app.a.X внутри функции:
app.b.y() -> 2
from app.a import XтребуетXв момент импорта — а модульpartially initialized, выполнен наполовину.ImportError.import app.aиapp.a.Xна уровне модуля — ошибка возникает ещё доX. Подмодуль становится атрибутом пакета, только когда выполнится целиком, аapp.aуже лежит вsys.modules, но выполнен наполовину — отсюдаcannot access submodule 'a' of module 'app'.import app.aиapp.a.Xвнутри функции — работает. К моменту вызова функции оба модуля выполнены до конца, иXуже есть.
Отсюда способы развязать цикл: обращаться к имени другого модуля в момент использования, а не импорта; перенести импорт внутрь функции; вынести общую часть в третий модуль, от которого зависят оба. Последний — обычно правильный: цикл чаще всего означает, что два модуля делят то, что должно жить отдельно.
Одна деталь версий. Если те же два файла лежат не в пакете, а прямо в папке запуска, то на 3.13 и 3.14 подсказка о цикле в тексте ошибки сменилась подсказкой о переименовании:
те же a.py и b.py (from-вариант) прямо в папке запуска — import a:
ImportError: cannot import name 'X' from 'a' (consider renaming '<папка>/a.py' if it has the same name as a library you intended to import)
На 3.11 и 3.12 в этом же месте был текст partially initialized module 'a' (most likely due to a circular import). Причина цикла та же — изменилась
только подсказка, и по новой её легко не узнать.
Механизм 5: свой файл с именем стандартного модуля
Папка запускаемого скрипта стоит в пути поиска модулей первой — строка
документации sys говорит:
path[0] is the script directory
(path[0] — папка скрипта). Файл
random.py, положенный рядом со скриптом game.py, находится раньше
стандартного модуля:
5) свой random.py рядом со скриптом
[выполняется мой random.py]
AttributeError: module 'random' has no attribute 'randint' (consider renaming '<папка>/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)
Выполнился свой файл, а не стандартный модуль, и random.randint в нём нет.
Подсказку о переименовании добавил 3.13; на 3.11 и 3.12 ошибка заканчивается
на has no attribute 'randint', и причину приходится угадывать. Классика
жанра — файлы random.py, email.py, csv.py или json.py, созданные
для экспериментов с одноимённым модулем.
Глубже: пакеты и цена импорта
Обычный пакет — папка с модулями и файлом __init__.py; этот файл —
тело самого пакета. Импорт подмодуля сначала выполняет пакет, а импорт
пакета подмодули сам не загружает:
6) пакет: __init__.py и подмодуль
import pkg.sub:
[pkg/__init__.py]
[pkg/sub.py]
pkg.sub.NAME -> sub
import pkg, затем pkg.sub:
[pkg/__init__.py]
hasattr(pkg, "sub") -> False
AttributeError: module 'pkg' has no attribute 'sub'
import json — подмодули загрузил json/__init__.py:
'json.decoder' in sys.modules -> True
'json.encoder' in sys.modules -> True
Если пакет хочет, чтобы pkg.sub был доступен после import pkg, его
__init__.py импортирует подмодуль сам. Так устроен json: после одного
import json его подмодули json.decoder и json.encoder уже загружены.
Поэтому import пакета может тянуть за собой больше модулей, чем видно в
коде.
Пакет без __init__.py — пакет-пространство имён (namespace package):
тела у него нет, а его части могут лежать в нескольких папках пути поиска.
Раздел 7 прогона — папка nspkg/ с одним файлом mod.py, а также встроенный
модуль:
пакет без __init__.py (namespace package):
[nspkg/mod.py]
nspkg.__file__ -> None
list(nspkg.__path__) -> ['<папка>/nspkg']
встроенный модуль и модуль-расширение:
sys.__spec__.origin -> 'built-in'
hasattr(sys, '__file__') -> False
_json -> встроен в интерпретатор
import nspkg.mod сработал без __init__.py; файла у такого пакета нет,
есть только список папок __path__. У sys нет и __file__: он встроен. А
вспомогательный _json в сборках 3.13.7 и 3.14.7 встроен, в сборках 3.11.15
и 3.12.3 — отдельный модуль-расширение .so: так это устроено в конкретных
сборках, а не в языке.
Цена. Первый импорт ищет файл, загружает код и выполняет тело; повторный
— поиск в sys.modules:
1) повторный импорт: import json внутри функции, нс на вызов
форма мин медиана макс
пустая функция 40.3 41.4 83.1
json из глобальных 43.0 44.3 91.8
import json внутри 122.0 125.7 185.8
import json сверх пустой функции по медианам: 84.3 нс
2) первый импорт в свежем интерпретаторе, мкс на оператор import
модуль мин медиана макс
import json 8220.2 9128.1 9720.3
import decimal 3160.0 3595.0 7370.9
import asyncio 51559.8 54409.2 69681.2
первый import json / повторный по медианам: 108256
Вызов функции с import json внутри стоит 125,7 нс, с json из глобальных
имён — 44,3 нс, пустой — 41,4: сам повторный импорт добавляет 84,3 нс. Это
дороже, чем глобальное имя, но это десятки наносекунд. Первый импорт json — 9,1 мс, в сто тысяч раз дороже, asyncio
— 54 мс. Поэтому импорт внутри функции используют не ради скорости вызова, а
ради старта: тяжёлый модуль, нужный в одной редкой команде, не стоит грузить
каждому запуску программы.
Как отвечать на собеседовании
Короткий ответ: import находит модуль и связывает имя. Тело модуля
выполняется при первой успешной загрузке; повторный import под тем же
полным именем в том же интерпретаторе берёт модуль из словаря sys.modules. from m import x копирует ссылку на значение в момент
импорта — новое присваивание m.x её не меняет. if __name__ == "__main__"
отделяет код запуска от кода, который выполнится при импорте.
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
На «сколько раз выполняется модуль». «Один раз на интерпретатор и
полное имя. В моём прогоне три импорта подряд и ещё тысяча в цикле выполнили
тело один раз: модуль лежит в sys.modules. Заново — importlib.reload,
новый import после del sys.modules[...], повторная попытка после
неудачной первой загрузки, другой процесс. И скрипт, который импортируют по
имени: он уже есть как __main__, и тело выполнится второй раз».
На «почему не видно нового значения после from». «from settings import DEBUG — это как DEBUG = settings.DEBUG один раз. Присваивание в модуле
перевязывает его имя, а моё остаётся на старом объекте. Изменяемый объект —
общий, его изменения видны».
На «как бороться с циклическим импортом». «Он падает, когда имя нужно в
момент импорта: from a import X из модуля, который ещё выполняется.
Работает, если обращаться a.X внутри функции. Правильное решение — вынести
общую часть в третий модуль».
На «import внутри функции — это медленно?» «Нет, после первого раза это поиск в словаре: у меня 84 нс. Медленный — первый импорт, миллисекунды. Поэтому тяжёлые модули импортируют внутри функции, чтобы ускорить старт».
Дальше спросят
Как перезагрузить модуль и что при этом не обновится?
importlib.reload(модуль) выполняет тело заново в том же объекте-модуле —
в прогоне строка тела напечаталась второй раз, а reload вернул тот же
объект. Но имена, взятые через from, по-прежнему указывают на старые
объекты: reload перевязывает имена внутри модуля, а не у тех, кто их
скопировал, — в прогоне старый ITEMS уже не был noisy.ITEMS.
Чем python -m tool отличается от python tool.py?
В обоих случаях модуль получает __name__ == "__main__" — в прогоне блок
выполнился оба раза. Разница в том, как модуль находится: -m ищет его по
имени, как import, поэтому так запускают модули пакетов
(python -m pkg.tool) и модули стандартной библиотеки — python -m json.tool,
python -m venv. У модуля пакета, запущенного через -m, заполнен
__package__, и работают относительные импорты; запущенный по пути файла —
ImportError: attempted relative import with no known parent package
(полный пример — в механизме 3).
Где ставить import — в начале файла или в функции?
По соглашению — в начале файла: зависимости модуля видны сразу. Внутрь функции импорт переносят по двум причинам: развязать циклический импорт и не грузить тяжёлый модуль, пока он не нужен. Повторный импорт внутри функции — десятки наносекунд, так что вызову он не мешает.
Частые заблуждения
import внутри функции заново выполняет модуль при каждом вызове
Тело модуля выполняется только при первом импорте. В прогоне ещё тысяча import в цикле не выполнили его ни разу, а повторный import json стоил 84,3 нс.
from settings import DEBUG даёт живую связь с переменной модуля
Это копия ссылки на значение в момент импорта. После settings.DEBUG = True импортированное DEBUG осталось False.
Циклический импорт всегда приводит к ошибке
Падает обращение к имени, которого ещё нет. import app.a с обращением app.a.X внутри функции работает: к моменту вызова оба модуля выполнены.
Модуль — это всегда файл .py, а пакет — всегда папка с __init__.py
Это типичные случаи. sys встроен и файла не имеет; модуль-расширение — файл .so или .pyd; папка без __init__.py импортируется как пакет-пространство имён: в прогоне import nspkg.mod сработал, а nspkg.__file__ — None.
import pkg загружает все подмодули пакета
Выполняется только pkg/__init__.py. В прогоне после import pkg было hasattr(pkg, "sub") -> False.
Имя своего файла ни на что не влияет
Файл random.py рядом со скриптом заслоняет стандартный модуль: import random находит его первым.
История версий
| Версия | Изменение | Что это значит для кода |
|---|---|---|
| 3.11 | Опорная точка урока. Поведение импорта во всех шести сюжетах на 3.11–3.14 одно и то же; меняются только тексты ошибок. | |
| 3.13 | В прогоне изменились два текста ошибок. При заслонённом стандартном модуле ошибка подсказывает переименовать файл: consider renaming '…/random.py' since it has the same name as the standard library module …. У файлов цикла прямо в папке запуска подсказка о цикле сменилась подсказкой о переименовании. Тесты, которые сравнивают эти тексты целиком, на 3.13 сломаются. | |
| 3.14 | В прогоне тексты ошибок те же, что на 3.13. В справочнике языка второй шаг импорта описан как связывание имени в текущем пространстве имён — так же, как это сделал бы оператор присваивания. | |
| 3.15 | Вышел 9 октября 2026; урок на нём не запускался. По release notes (PEP 810), explicit lazy imports using the new lazy soft keyword … defers the actual module loading until the imported name is first used (явные ленивые импорты с новым мягким ключевым словом lazy … откладывают настоящую загрузку модуля до первого использования импортированного имени). Всё, что урок говорит о выполнении тела модуля в момент import, относится к обычному, немедленному импорту — без lazy. |
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.
Практика · что напечатает
import config from config import DEBUG config.DEBUG = True print(DEBUG, config.DEBUG)
Практика · оцените
Проверьте себя
Модуль db.py на верхнем уровне открывает соединение с базой. Его импортируют пять модулей программы. Сколько соединений откроется?
Чем измерено
Числа и наблюдения этого урока получены этими скриптами. Каждый открывается прямо отсюда — вместе с записью прогона: на чём считали, что получилось и с каким разбросом.
Прогоны: CPython 3.11.15, 3.12.3, 3.13.7 и 3.14.7, Linux x86_64, одна машина (2 ядра); цитаты документации сверены 14.10.2026. Python 3.15 к этой дате вышел, но в среде замеров его нет.
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- Модуль — обычно файл
.py, аimportделает две вещи: находит модуль, выполняя его тело при первой успешной загрузке, и связывает имя. Загруженный модуль хранится в словареsys.modulesтекущего интерпретатора, и повторный успешныйimportпод тем же полным именем берёт оттуда готовый объект. В прогоне триimport noisyподряд и ещё тысяча в цикле выполнили тело модуля ровно один раз; после удаления записи изsys.modulesследующийimportвыполнил его снова. - Отсюда главное следствие:
from settings import DEBUG— это присваиваниеDEBUG = settings.DEBUGв момент импорта, а не живая связь с модулем. Послеsettings.DEBUG = Trueимпортированное имяDEBUGосталосьFalse. Список, импортированный так же, — общий:appendчерез модуль виден, потому что объект один. - Дальше — то, что отличает понимание от пересказа. Циклический импорт падает не всегда:
from app.a import Xиз недоимпортированного модуля даётImportError, аimport app.aс обращениемapp.a.Xвнутри функции работает. Свой файлrandom.pyрядом со скриптом заслоняет стандартный модуль — начиная с 3.13 ошибка об этом прямо подсказывает. А повторныйimportвнутри функции в замере на CPython 3.13.7 стоил 84,3 нс сверх пустого вызова, тогда как первыйimport jsonв свежем интерпретаторе — 9,1 мс. - Прогоны — CPython 3.11.15, 3.12.3, 3.13.7 и 3.14.7 на Linux x86_64; сверено 14.10.2026. В вышедшем к этой дате Python 3.15 появились явные ленивые импорты (PEP 810); урок описывает обычный, немедленный
importи на 3.15 не запускался.
На самом деле
- Тело модуля выполняется только при первом импорте. В прогоне ещё тысяча
importв цикле не выполнили его ни разу, а повторныйimport jsonстоил 84,3 нс. - Это копия ссылки на значение в момент импорта. После
settings.DEBUG = TrueимпортированноеDEBUGосталосьFalse. - Падает обращение к имени, которого ещё нет.
import app.aс обращениемapp.a.Xвнутри функции работает: к моменту вызова оба модуля выполнены. - Это типичные случаи.
sysвстроен и файла не имеет; модуль-расширение — файл.soили.pyd; папка без__init__.pyимпортируется как пакет-пространство имён: в прогонеimport nspkg.modсработал, аnspkg.__file__—None. - Выполняется только
pkg/__init__.py. В прогоне послеimport pkgбылоhasattr(pkg, "sub") -> False. - Файл
random.pyрядом со скриптом заслоняет стандартный модуль:import randomнаходит его первым.
По версиям
- 3.11
- Опорная точка урока. Поведение импорта во всех шести сюжетах на 3.11–3.14 одно и то же; меняются только тексты ошибок.<
- 3.13
- В прогоне изменились два текста ошибок. При заслонённом стандартном модуле ошибка подсказывает переименовать файл:
consider renaming '…/random.py' since it has the same name as the standard library module …. У файлов цикла прямо в папке запуска подсказка о цикле сменилась подсказкой о переименовании. Тесты, которые сравнивают эти тексты целиком, на 3.13 сломаются.< - 3.14
- В прогоне тексты ошибок те же, что на 3.13. В справочнике языка второй шаг импорта описан как связывание имени в текущем пространстве имён — так же, как это сделал бы оператор присваивания.<
- 3.15
- Вышел 9 октября 2026; урок на нём не запускался. По release notes (PEP 810), explicit lazy imports using the new lazy soft keyword … defers the actual module loading until the imported name is first used. Всё, что урок говорит о выполнении тела модуля в момент
import, относится к обычному, немедленному импорту — безlazy.<
Что разобрано
- База: модуль — обычно файл, а import — две операции
- Механизм 1: модуль выполняется один раз — sys.modules
- Механизм 2: from m import x копирует ссылку
- Механизм 3: `__name__ == "__main__"`
- Механизм 4: циклический импорт — где он падает, а где нет
- Механизм 5: свой файл с именем стандартного модуля
- Глубже: пакеты и цена импорта
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- История версий
- Практика
- Проверьте себя
- Чем измерено
Источники и что читать дальше
4 ИСТОЧНИКА
- Справочник языка — оператор importОфициальная документация. Из чего состоит import: «The basic import statement (no "from" clause) is executed in two steps: 1. find a module, loading and initializing it if necessary 2. define a name or names in the local namespace for the scope where the "import" statement occurs» (Простой оператор import, без from, выполняется в два шага: 1) найти модуль, при необходимости загрузив и инициализировав его; 2) определить имя или имена в локальном пространстве имён той области, где стоит оператор import). И что делает from для каждого имени: «a reference to that value is stored in the local namespace» (ссылка на это значение сохраняется в локальном пространстве имён). В тексте 3.14 вместо local стоит current, а ко второму шагу добавлено: «just as an assignment statement would» (так же, как это сделал бы оператор присваивания). Текст сверен по встроенной справке pydoc (модуль pydoc_data.topics) CPython 3.13.7 и 3.14.7.https://docs.python.org/3.13/reference/simple_stmts.html#the-import-statement
- Модель исполнения и модель данных — имя модуляОфициальная документация. Когда создаётся модуль и как называется главный: «The namespace for a module is automatically created the first time a module is imported. The main module for a script is always called "__main__"» (Пространство имён модуля создаётся автоматически при первом импорте модуля. Главный модуль скрипта всегда называется __main__). Об атрибуте __name__: «The name used to uniquely identify the module in the import system. For a directly executed module, this will be set to "__main__"» (Имя, по которому модуль однозначно опознаётся системой импорта. У модуля, запущенного напрямую, оно равно __main__). Сверено по pydoc_data.topics 3.13.7 и 3.14.7.https://docs.python.org/3.13/reference/executionmodel.html#naming-and-binding
- sys.modules и importlib.reloadОфициальная документация. Строки документации в интерпретаторе. Модуль sys: «modules -- dictionary of loaded modules» (modules — словарь загруженных модулей). Функция importlib.reload: «Reload the module and return it. The module must have been successfully imported before» (Перезагрузить модуль и вернуть его. Модуль должен быть до этого успешно импортирован). И откуда ищутся модули: «path -- module search path; path[0] is the script directory, else ''» (path — путь поиска модулей; path[0] — папка скрипта, иначе пустая строка). Сверено вызовами sys.__doc__ и importlib.reload.__doc__ на 3.13.7 и 3.14.7.https://docs.python.org/3.13/library/importlib.html#importlib.reload
- Что нового в Python 3.15 — явные ленивые импорты (PEP 810)Официальная документация. Python 3.15 вышел 9 октября 2026. Release notes: «explicit
lazyimports using the newlazysoft keyword … defers the actual module loading until the imported name is first used» (явные ленивые импорты с новым мягким ключевым словом lazy … откладывают настоящую загрузку модуля до первого использования импортированного имени). Цитата сверена 14.10.2026. Урок на 3.15 не запускался: в среде замеров этой версии нет; всё, что урок говорит о выполнении тела модуля, относится к обычному import.https://docs.python.org/3.15/whatsnew/3.15.html