EasyТеория3 min

Модули и система импортов

Инструкции import и from, атрибут __name__, управление экспортом через __all__ и работа с sys.path

В Python каждый файл с расширением .py является модулем. Модули позволяют разделять код на логические части, переиспользовать функции и классы между проектами и управлять пространством имен. Система импортов -- одна из фундаментальных частей языка.

Что такое модуль

Модуль -- это файл Python, содержащий определения функций, классов и переменных. При импорте модуля Python выполняет весь код в файле один раз и кэширует результат.

# math_utils.py — a simple module
def add(a: float, b: float) -> float:
    """Return the sum of two numbers."""
    return a + b

def multiply(a: float, b: float) -> float:
    """Return the product of two numbers."""
    return a * b

PI = 3.14159265358979

Инструкция import

Базовый способ подключения модуля -- инструкция import. Она загружает модуль целиком и создает ссылку на него в текущем пространстве имен.

import math_utils

result = math_utils.add(2, 3)       # 5
pi = math_utils.PI                  # 3.14159...

Импорт нескольких модулей

Каждый модуль рекомендуется импортировать отдельной строкой (PEP 8):

# Good practice — one import per line
import os
import sys
import json

# Acceptable for closely related modules
from os import path, getcwd

Псевдонимы (as)

Оператор as позволяет задать короткое имя для модуля:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Standard aliases in the Python ecosystem
data = np.array([1, 2, 3])

Инструкция from ... import

Позволяет импортировать конкретные имена из модуля напрямую:

from math_utils import add, PI

result = add(2, 3)    # No prefix needed
print(PI)             # 3.14159...

Импорт всех имен (*)

Импорт через * подтягивает все публичные имена модуля. Это считается плохой практикой для production-кода, так как засоряет пространство имен:

# Avoid in production code
from math_utils import *

# You don't know where 'add' came from when reading code
result = add(2, 3)

Атрибут __name__

Каждый модуль имеет встроенный атрибут __name__. Когда модуль запускается напрямую, __name__ равен "__main__". Когда модуль импортируется, __name__ содержит имя модуля.

# greet.py
def greet(name: str) -> str:
    """Return a greeting message."""
    return f"Hello, {name}!"

# This block runs ONLY when the file is executed directly
if __name__ == "__main__":
    # Will NOT run when imported as a module
    message = greet("World")
    print(message)

Это стандартный паттерн, позволяющий использовать файл и как модуль, и как скрипт:

python greet.py        # Prints "Hello, World!"
import greet           # Does NOT print anything
greet.greet("Python")  # "Hello, Python!"

Атрибут __all__

Переменная __all__ -- это список строк, определяющий, какие имена экспортируются при from module import *:

# validators.py
__all__ = ["validate_email", "validate_phone"]

def validate_email(email: str) -> bool:
    """Check if email format is valid."""
    return "@" in email and "." in email

def validate_phone(phone: str) -> bool:
    """Check if phone format is valid."""
    return phone.startswith("+") and len(phone) >= 10

def _normalize_input(value: str) -> str:
    """Internal helper — not exported."""
    return value.strip().lower()
from validators import *

validate_email("[email protected]")  # Available
_normalize_input("test")            # NOT available (not in __all__)

Путь поиска модулей (sys.path)

Python ищет модули в определенном порядке. Список директорий для поиска хранится в sys.path:

import sys

for path in sys.path:
    print(path)

Порядок поиска:

  1. Директория запускаемого скрипта (или текущая директория)
  2. Переменная окружения PYTHONPATH
  3. Директории стандартной библиотеки
  4. Директория site-packages (установленные пакеты)

Динамическое добавление путей

import sys

# Add a custom directory to the search path
sys.path.insert(0, "/path/to/my/modules")

# Now Python will also search in this directory
import my_custom_module

Относительные и абсолютные импорты

В пакетах (директориях с __init__.py) можно использовать относительные импорты:

mypackage/
    __init__.py
    module_a.py
    module_b.py
    subpackage/
        __init__.py
        module_c.py
# module_c.py — relative imports within a package
from . import helpers           # From current package
from .. import module_a         # From parent package
from ..module_b import some_func  # Specific name from parent

Абсолютные импорты -- предпочтительный стиль (PEP 8):

# Absolute import — always works, always clear
from mypackage.subpackage import module_c
from mypackage.module_a import MyClass

Кэширование модулей

Python кэширует импортированные модули в словаре sys.modules. Повторный import не перезагружает модуль:

import sys
import json

# Module is cached after first import
print("json" in sys.modules)  # True

# Force reload (useful in development/REPL)
import importlib
importlib.reload(json)

Встроенные модули Python

Python поставляется с богатой стандартной библиотекой:

import os           # Operating system interface
import sys          # System-specific parameters
import json         # JSON encoder/decoder
import pathlib      # Object-oriented filesystem paths
import datetime     # Date and time handling
import collections  # Specialized container types
import itertools    # Iterator building blocks
import functools    # Higher-order functions
import typing       # Type hint support
import dataclasses  # Data class decorator

Порядок импортов (PEP 8)

Стандартная конвенция разделяет импорты на три группы:

# 1. Standard library imports
import os
import sys
from pathlib import Path

# 2. Third-party library imports
import requests
import numpy as np
from pydantic import BaseModel

# 3. Local application imports
from myapp.models import User
from myapp.utils import validate

Инструменты вроде isort и ruff автоматизируют сортировку импортов.

Циклические импорты

Циклические импорты возникают, когда два модуля импортируют друг друга. Это частая ошибка новичков:

# a.py
from b import func_b  # Tries to import b, which imports a...

def func_a():
    return func_b()

# b.py
from a import func_a  # Circular dependency!

def func_b():
    return func_a()

Решения:

  • Вынести общий код в третий модуль
  • Использовать отложенный импорт (внутри функции)
  • Пересмотреть архитектуру
# Lazy import — breaks the cycle
def func_a():
    from b import func_b  # Import at call time, not at module load
    return func_b()

Проверь себя

Чему равен __name__ модуля, когда он запущен напрямую как скрипт?

Что определяет переменная __all__ в модуле?

Как правильно решить проблему циклического импорта?

Какой порядок импортов рекомендует PEP 8?

Что произойдет, если модуль импортируется повторно через import?