Deep Engineering
Средний·Опубликовано·3.11 · 3.12 · 3.13 · 3.14·35 МИН

Модули и импорт: модуль выполняется один раз, а 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:

PYTHON
# 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:

  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.

— Справочник языка — оператор import

Ключевые слова здесь — «при необходимости». Механизмы ниже — о том, когда эта необходимость есть и что из этого следует.

Как читать блоки кода: блок с пометкой «полный файл» копируется как есть; блок без языка — дословный вывод прогона из bench/imports/runs/; строки, начинающиеся с отступа без заголовка раздела, — фрагмент этого вывода.

Механизм 1: модуль выполняется один раз — sys.modules

контракт языкаКэш загруженных модулей и повторное выполнение через reload — строки документации sys и importlib; вывод — прогон bench/imports/basics.py, разделы 1 и 7, одинаковые на CPython 3.11–3.14 (Linux x86_64).

Модуль 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 кладёт в пространство имён — справочник языка, оператор import; вывод — прогон bench/imports/basics.py, раздел 2, одинаковый на CPython 3.11–3.14.

Для формы from документация описывает, что происходит с каждым именем:

a reference to that value is stored in the local namespace

перевод

Ссылка на это значение сохраняется в локальном пространстве имён.

— Справочник языка — оператор import

Ссылка на значение — не на имя в модуле. 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__"

контракт языкаИмя главного модуля и атрибут __name__ — модель исполнения и модель данных; вывод — прогон bench/imports/basics.py, разделы 3 и 7, одинаковые на CPython 3.11–3.14.

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/.

PYTHON
# shop/__init__.py — полный файл
PRICE = 10
PYTHON
# 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: циклический импорт — где он падает, а где нет

контракт языкаВывод — прогон bench/imports/basics.py, раздел 4; модули цикла лежат в пакете app/. Тексты ошибок для пакета одинаковы на CPython 3.11–3.14; для файлов прямо в папке запуска текст на 3.13 и 3.14 другой — см. ниже.

Модуль 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; вывод — прогон bench/imports/basics.py, раздел 5: подсказка в тексте ошибки есть на CPython 3.13 и 3.14, на 3.11 и 3.12 её нет.

Папка запускаемого скрипта стоит в пути поиска модулей первой — строка документации 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, созданные для экспериментов с одноимённым модулем.

Глубже: пакеты и цена импорта

наблюдение замераПакет — bench/imports/basics.py, разделы 6 и 7, одинаково на CPython 3.11–3.14, кроме строки про _json: встроен он или лежит отдельным файлом, зависит от сборки. Цена — bench/imports/cost.py: медианы девяти кругов, CPython 3.13.7 на Linux x86_64, одна машина (2 ядра); 3.14.7 — в записи cost-3.14.txt. Это наблюдения одной конфигурации: на другой машине и версии числа и их отношения будут другими — мерить у себя.

Обычный пакет — папка с модулями и файлом __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.

Практика

Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.

Практика · что напечатает

В config.py одна строка: DEBUG = False. Что напечатает этот код?
import config
from config import DEBUG
config.DEBUG = True
print(DEBUG, config.DEBUG)

Практика · оцените

Тело модуля counter увеличивает счётчик на единицу. Модуль импортируют 1000 раз в цикле, потом дважды вызывают importlib.reload. Сколько раз выполнилось тело?
раз

Проверьте себя

Вопрос 1 из 4

Модуль 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 к этой дате вышел, но в среде замеров его нет.

Источники и что читать дальше

4 ИСТОЧНИКА

  1. Справочник языка — оператор 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
  2. Модель исполнения и модель данных — имя модуляОфициальная документация. Когда создаётся модуль и как называется главный: «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
  3. 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
  4. Что нового в Python 3.15 — явные ленивые импорты (PEP 810)Официальная документация. Python 3.15 вышел 9 октября 2026. Release notes: «explicit lazy imports using the new lazy soft 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