Модули и импорт
Модуль — это файл и объект
Любой файл something.py — модуль. При импорте Python выполняет его код и создаёт объект-модуль, атрибуты которого — все глобальные имена файла:
import math
print(type(math), math.__name__)
print(math.sqrt(16), math.pi)
print([name for name in dir(math) if not name.startswith("_")][:8], "...")
print(math.__file__ if hasattr(math, "__file__") else "встроен в интерпретатор")
Формы импорта
import json # имя json → объект-модуль
import collections.abc # загружает пакет и подмодуль, имя — collections
import datetime as dt # псевдоним
from pathlib import Path # имя Path → объект из модуля
from os.path import join as path_join # конкретное имя с псевдонимом
print(json.dumps([1]), collections.abc.Iterable, dt.date(2026, 1, 1))
print(Path("a") / "b", path_join("a", "b"))
| Запись | Что появляется в вашем пространстве имён |
|---|---|
import json |
json |
import os.path |
os (а os.path доступен через атрибут) |
import numpy as np |
np |
from json import dumps |
dumps (модуль json загружен, но имени нет) |
from json import * |
все публичные имена — непредсказуемо |
from module import *
Засоряет пространство имён, может молча затереть ваши переменные, ломает статический анализ («откуда взялось это имя?»). Допустимо только в интерактивной консоли и в __init__.py с явным __all__.
from-импорт копирует ссылку
import types, sys
settings = types.ModuleType("settings") # создаём модуль на лету для примера
settings.DEBUG = False
sys.modules["settings"] = settings
from settings import DEBUG # ссылка на объект False
import settings as s
s.DEBUG = True # меняем атрибут модуля
print(DEBUG, s.DEBUG) # наша копия ссылки не изменилась
Отсюда правило для изменяемых настроек и для моков в тестах: патчить нужно имя там, где оно используется, а не где определено.
Модуль выполняется один раз
При первом import x Python выполняет файл и кладёт модуль в словарь sys.modules. Все последующие импорты берут готовый объект из кеша:
import sys, tempfile, pathlib
folder = pathlib.Path(tempfile.mkdtemp())
(folder / "greeter.py").write_text(
'print(" выполняется код модуля greeter")\n'
'counter = 0\n'
'def hello(name):\n'
' global counter\n'
' counter += 1\n'
' return f"Привет, {name}!"\n',
encoding="utf-8",
)
sys.path.insert(0, str(folder)) # добавляем папку в путь поиска модулей
import greeter
import greeter # второй раз код модуля не выполняется
from greeter import hello
print(hello("Анна"), greeter.counter)
print("greeter" in sys.modules, sys.modules["greeter"] is greeter)
import importlib
greeter = importlib.reload(greeter) # принудительно выполнить заново
print("после reload counter =", greeter.counter)
Поэтому модуль — естественный синглтон: объект config, созданный в модуле, один на всё приложение.
Как Python ищет модуль
sys.modules— уже загружен? Вернуть.- Встроенные модули (
sys,builtins). sys.path— список папок, по порядку: - папка запускаемого скрипта (или текущая приpython -m); -PYTHONPATH; - стандартная библиотека; -site-packagesтекущего окружения (то, что поставил pip).
import sys
for p in sys.path[:6]:
print(repr(p))
Не называйте файлы как стандартные модули
Файл random.py или json.py в папке проекта перекроет стандартный модуль: import random найдёт ваш файл первым. Классическая ошибка новичков: «AttributeError: module 'random' has no attribute 'randint'».
name и main
У каждого модуля есть __name__. При импорте это имя модуля ("greeter"), а у файла, запущенного как программа, — "__main__":
# tools.py
def slugify(text):
return text.lower().replace(" ", "-")
def main():
print(slugify("Hello World"))
if __name__ == "__main__": # True только при `python tools.py`
main() # при `import tools` не выполнится
python -m package.module запускает модуль как программу, правильно настраивая sys.path и относительные импорты — предпочтительный способ запуска модулей внутри пакетов.
all
Список __all__ определяет, что экспортирует from module import *, и документирует публичный API:
# geometry.py
__all__ = ["area", "Circle"]
def area(r): ...
class Circle: ...
def _helper(): ... # с подчёркиванием — внутреннее
Циклические импорты
# a.py # b.py
from b import func_b from a import func_a # ← a ещё не выполнен до конца!
def func_a(): ... def func_b(): ...
Когда a импортирует b, а b — a, второй получает недоделанный модуль a (из sys.modules), в котором ещё нет func_a → ImportError: cannot import name ... (most likely due to a circular import).
Решения:
- Вынести общее в третий модуль — лучший вариант, обычно цикл указывает на плохое разделение ответственности.
- Импортировать внутри функции — импорт выполнится при вызове, когда оба модуля уже загружены.
import bвместоfrom b import func_bи обращениеb.func_b()во время вызова.- Для подсказок типов —
from __future__ import annotationsиif TYPE_CHECKING:.
Динамический и ленивый импорт
import importlib
for name in ["json", "csv", "nonexistent_lib"]:
try:
module = importlib.import_module(name)
print(name, "→", module)
except ModuleNotFoundError as e:
print(name, "→", e)
def export_excel(rows):
import decimal # тяжёлую зависимость импортируем только при вызове
return decimal.Decimal(len(rows))
print(export_excel([1, 2, 3]))
Импорт внутри функции ускоряет запуск CLI-утилит и разрывает циклы, но прячет зависимости — используйте осознанно.
Вопрос на собеседовании
«Что происходит при import x?» — Python проверяет sys.modules; если модуля нет, ищет его по sys.path (finder), загружает (loader), создаёт объект-модуль, кладёт в sys.modules, выполняет код модуля и связывает имя x в текущем пространстве имён. Повторный импорт берёт модуль из кеша.
Итог
- Модуль — файл
.pyи объект, его атрибуты — глобальные имена файла. - Код модуля выполняется один раз, затем модуль берётся из
sys.modules. - Поиск:
sys.modules→ встроенные →sys.path; не называйте файлы как стандартные модули. if __name__ == "__main__":— код только для запуска как программы.- Избегайте
import *; циклы разрывайте выносом общего кода или импортом внутри функции.
Практика решено 0 из 5
Функция module_info(name) должна импортировать модуль по имени-строке (через importlib)
и вернуть кортеж (module.__name__, количество публичных имён), где публичные имена —
элементы dir(module), не начинающиеся с _.
Какие тесты проверяют код (3)
import math; assert module_info("math") == ("math", len([n for n in dir(math) if not n.startswith("_")]))
assert module_info("os.path")[0] in ("posixpath", "ntpath")
try:
module_info("no_such_module_xyz")
ok = False
except ModuleNotFoundError:
ok = True
assert ok
import importlib
def module_info(name):
module = importlib.import_module(name)
public = [n for n in dir(module) if not n.startswith("_")]
return module.__name__, len(public)
Модуль config.py содержит print("загружаю"). Что выведет программа?
import config
import config
from config import *
Зачем нужна проверка if __name__ == "__main__":?
Что выведет код?
from math import pi
import math
math.pi = 3
print(pi, math.pi)
Как можно разорвать циклический импорт между a.py и b.py?
Войдите, чтобы отмечать пройденные уроки.