Модули в Python

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

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

Модуль в Python — это просто файл с расширением .py, содержащий код Python (функции, классы, переменные), который можно импортировать и использовать в других программах.

Создание собственного модуля и его импорт

Создать модуль в Python очень просто: достаточно написать код в файле с расширением .py. Заведём mymath.py и положим рядом несколько main-скриптов, которые показывают разные способы его подключить:

main_basic.py
1# Импорт всего модуля2import mymath3 4# Все имена доступны через префикс mymath.5result = mymath.add(5, 3)6print(f"5 + 3 = {result}")  # 5 + 3 = 87 8area = mymath.PI * 5 ** 29print(f"Площадь круга радиусом 5: {area}")10# Площадь круга радиусом 5: 78.5397511 

В дереве слева наш модуль mymath.py и четыре варианта main_*.py, каждый показывает свой способ импорта. Переключайтесь между вкладками. Ниже короткий разбор, когда какой удобнее.

Импорт всего модуля

Самый прямой способ: import mymath (см. main_basic.py). Все имена остаются «внутри» модуля и доступны через префикс: mymath.add, mymath.PI. Удобно, когда из одного модуля нужно много функций и важно видеть, откуда они пришли.

Импорт конкретных элементов

from mymath import add, multiply (см. main_specific.py). Импортируем только нужное и обращаемся без префикса. Хорошо для пары часто вызываемых функций; плохо, когда через сотню строк забываешь, откуда взялась add.

Импорт с переименованием

import mymath as mm (см. main_aliased.py). Нужно, если имя модуля длинное (numpy as np, pandas as pd те самые случаи) или конфликтует с локальной переменной.

Импорт всех элементов

from mymath import * (см. main_star.py). Импортирует всё подряд в текущее пространство имён. В обычном коде так делать обычно не стоит: теряется источник имён, легко получить конфликт. Нормально только в REPL и иногда в тестах.

Где Python ищет модули

Когда вы пишете import mymath, Python ищет файл mymath.py по списку директорий из sys.path. По умолчанию туда входят:

  1. Директория, из которой запущен скрипт (или текущая директория в REPL).
  2. Встроенные модули (math, os, ...) — они часть Python.
  3. site-packages — папка, куда pip install устанавливает сторонние пакеты.

Поиск останавливается на первой найденной директории. Это значит, что если рядом со скриптом лежит файл с тем же именем, что и стандартный модуль, Python подхватит ваш, а не системный. Классическая ловушка: создать random.py в проекте и потом долго удивляться, почему random.randint не работает.

Содержимое sys.path можно посмотреть так:

Python 3.13
import sys
print(sys.path)

Специальные переменные модуля

В Python модули имеют несколько специальных переменных.

Переменная __name__: модуль как программа vs как зависимость

Внутри Python у каждого модуля есть переменная __name__. Когда модуль импортируют, в ней лежит его имя ("mymath"). А когда модуль запускают напрямую (python mymath.py), Python кладёт в неё специальное значение "__main__". Это позволяет внутри модуля написать блок «делай это только при прямом запуске»:

mymath.py
1"""Модуль с математическими функциями."""2 3PI = 3.141594 5def add(a, b):6    return a + b7 8def divide(a, b):9    if b == 0:10        raise ValueError("Деление на ноль невозможно")11    return a / b12 13# Этот блок выполняется только при прямом запуске:14#   python mymath.py15# При импорте (import mymath) в другом файле он не сработает.16if __name__ == "__main__":17    print(f"PI = {PI}")18    print(f"add(2, 3) = {add(2, 3)}")19 

Запустите python mymath.py, и увидите вывод if-блока. Запустите python main.py, и этот блок промолчит: в выводе будет только 8 от вызова в main.py.

Переменная __all__: что попадает в from module import *

По умолчанию from mymath import * импортирует все имена, которые не начинаются с подчёркивания. Если хочется явно зафиксировать публичный API модуля, добавляют переменную __all__ со списком имён.

mymath.py
1"""Модуль с математическими функциями."""2 3# Только эти имена попадут в from mymath import *4__all__ = ["PI", "add"]5 6PI = 3.141597_INTERNAL = "не должен попасть наружу"8 9def add(a, b):10    return a + b11 12def subtract(a, b):13    return a - b14 15def _round_helper(value):16    """Внутренний помощник, не для публичного использования."""17    return round(value, 2)18 

Через from mymath import * пришли только PI и add, именно те, что перечислены в __all__. Остальные имена (subtract, _INTERNAL, _round_helper) остались в модуле и доступны только при явном импорте: from mymath import subtract.

Пакеты

Когда модуль mymath.py разрастается (например, появляются разные группы операций), его можно разделить на несколько файлов и собрать в пакет. Пакет в Python это директория с файлом __init__.py. Когда вы пишете import mathlib, Python видит этот __init__.py и понимает, что директория это импортируемый пакет.

Превратим наш модуль в пакет: разнесём функции по тематикам, а в __init__.py соберём публичный интерфейс.

mathlib/__init__.py
1"""mathlib: пакет с математическими функциями."""2 3__version__ = "0.1"4 5# Re-export, чтобы пользователи могли писать from mathlib import add6from mathlib.basic import PI, add, subtract7from mathlib.advanced import multiply, divide8 

Что изменилось:

  • Файлов теперь четыре, но снаружи это всё ещё «один математический модуль»: пользователь пишет from mathlib import add, как и раньше с mymath.
  • mathlib/__init__.py контролирует, что считается публичным API: имена, перечисленные через from .basic import ... и from .advanced import ..., доступны прямо как mathlib.add.
  • Внутри пакета модули могут ссылаться друг на друга через относительные импорты: from . import basic (тот же каталог), from .. import other (родительский каталог).

Если пакет растёт ещё, его можно разбить на подпакеты: например, mathlib/stats/ со своим __init__.py и модулями статистических функций. Принцип тот же, просто на уровень глубже.

Как организовать модуль

Несколько практик, которые делают модуль удобным и для вас, и для других читателей.

Одна ответственность. Каждый модуль отвечает за одну конкретную задачу: data_processing.py, auth.py, formatters.py. Если в файле уже две несвязанные темы, пора разделять.

Понятные имена. Описательные, но короткие, в стиле snake_case: user_interface.py, не ui.py и не UserInterface.py.

Порядок содержимого внутри файла:

  1. Docstring: описание модуля в тройных кавычках в самом начале.
  2. Импорты: сначала стандартная библиотека, потом сторонние пакеты, потом собственные модули.
  3. Константы: глобальные значения, которые не меняются.
  4. Классы и функции: основное содержимое.
  5. Блок if __name__ == "__main__":: код, который выполняется только при прямом запуске файла.

Явные импорты. Предпочитайте from module import specific_thing вместо from module import *: видно, что именно используется.

Приватные имена с _. Функции и переменные «для внутреннего пользования» начинайте с подчёркивания (_helper, _INTERNAL_CONST). Это сигнал для других: «не полагайтесь на это снаружи».

Проверка понимания

Что будет выведено при запуске main.py из проекта ниже?

main.py
1import test_module2 3print(test_module.hello())4