Модули и импорт

⏱ 35 мин · 🧩 заданий: 5

Модуль — это файл и объект

Любой файл 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 ищет модуль

  1. sys.modules — уже загружен? Вернуть.
  2. Встроенные модули (sys, builtins).
  3. 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).

Решения:

  1. Вынести общее в третий модуль — лучший вариант, обычно цикл указывает на плохое разделение ответственности.
  2. Импортировать внутри функции — импорт выполнится при вызове, когда оба модуля уже загружены.
  3. import b вместо from b import func_b и обращение b.func_b() во время вызова.
  4. Для подсказок типов — 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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Функция 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

Задание 2 Один вариант ответа

Модуль config.py содержит print("загружаю"). Что выведет программа?

import config
import config
from config import *
Задание 3 Один вариант ответа

Зачем нужна проверка if __name__ == "__main__":?

Задание 4 Один вариант ответа

Что выведет код?

from math import pi
import math
math.pi = 3
print(pi, math.pi)
Задание 5 Несколько вариантов

Как можно разорвать циклический импорт между a.py и b.py?

Войдите, чтобы отмечать пройденные уроки.