Модульное программирование — это способ организации кода, при котором программа делится на отдельные части (модули), каждая из которых отвечает за свою узкую задачу и общается с остальными через небольшой описанный набор функций и данных (интерфейс). Это не про конкретный язык и не про ООП, а про принцип разбиения: как провести границы между частями, чтобы менять одну из них, не задевая остальные.
Содержание
Ниже разберем, что считать модулем, чем интерфейс отличается от реализации, что такое связность и связанность (cohesion и coupling) и как все это выглядит в Python на примере import и файла __init__.py.
Модуль: интерфейс и реализация
Модуль — это законченный блок программы с одной ответственностью: расчет цены, работа с датами, доступ к базе. У модуля есть две стороны.
Интерфейс — то, что модуль показывает наружу: имена функций, их параметры и результат. Это контракт, на который опираются другие части программы.
Реализация — то, как модуль устроен внутри: конкретные формулы, вспомогательные переменные, временные структуры. Реализацию можно переписать полностью, и пока интерфейс не изменился, остальной код трогать не нужно.
Разделение этих двух сторон — главная идея, которую в 1972 году сформулировал Дэвид Парнас под названием «сокрытие информации» (information hiding): модулю про соседей достаточно знать только их интерфейс, а не внутренности. Именно это делает большие программы управляемыми.
Связность и связанность: cohesion vs coupling
Два близких по звучанию русских термина легко перепутать, поэтому разведем их сразу. Это разные метрики, и цель у них противоположная.
- Связность (cohesion) — насколько содержимое одного модуля посвящено одной задаче. Хотим высокую: модуль
pricingсчитает только цены, а не заодно шлет письма. - Связанность (coupling) — насколько сильно модули зависят друг от друга. Хотим низкую: модуль
ordersвызывает уpricingпару функций и ничего не знает про его внутренние переменные.
Мнемоника: высокая связность ВНУТРИ модуля, низкая связанность МЕЖДУ модулями. Один модуль собран вокруг своей темы, а связи с соседями узкие и проходят через интерфейс.
| Свойство | Что измеряет | Хорошо, когда | Симптом плохого |
|---|---|---|---|
| Связность (cohesion) | фокус одного модуля на одной задаче | высокая: модуль про одно | «модуль-свалка»: и парсинг, и почта, и логи вместе |
| Связанность (coupling) | зависимость модулей друг от друга | низкая: связь через узкий интерфейс | правка в одном модуле ломает три соседних |
Практическое следствие: если поправка в одном файле регулярно вынуждает лезть в несколько других, это высокая связанность, и границы модулей проведены неудачно.
Зачем разбивать программу на модули
Даже небольшой проект без структуры быстро становится трудным для правок: имена конфликтуют, непонятно, где что менять. Разбиение на модули решает конкретные задачи.
| Что дает модульность | За счет чего |
|---|---|
| Переиспользование | готовый модуль подключается в другом проекте одной строкой импорта |
| Тестируемость | узкий интерфейс легко покрыть автотестами, не поднимая всю программу |
| Командная работа | разные люди правят разные модули, не мешая друг другу |
| Изоляция ошибок | сбой локализован в одном модуле, а не расползается по коду |
| Нет конфликта имен | у каждого модуля свое пространство имен |
Обратная сторона тоже есть, и это не «только плюсы»: слишком мелкое дробление плодит лишние связи и запутанные вызовы между модулями, а неудачно проведенные границы дают высокую связанность. Модульность — инструмент, а не самоцель; выигрыш появляется при разумных границах.
Модули и пакеты в Python
В Python модуль — это просто файл .py. Его имя (без расширения) и есть имя модуля. Соберем маленький пример из двух файлов в одной папке.
Файл pricing.py — модуль с высокой связностью, в нем только про цену:
# pricing.py
VAT = 0.20 # ставка НДС
def with_vat(base):
return round(base * (1 + VAT), 2)
def apply_discount(price, percent):
return round(price * (1 - percent / 100), 2)
Файл main.py подключает нужные функции через import и пользуется только интерфейсом, не зная про переменную VAT внутри:
# main.py
from pricing import with_vat, apply_discount
full = with_vat(1000)
final = apply_discount(full, 10)
print(full, final)
Запуск python3 main.py печатает результат:
1200.0 1080.0
Здесь with_vat и apply_discount — интерфейс модуля pricing, а формула с VAT — его реализация. Связанность низкая: main обращается к соседу через две функции.
Пакет — это каталог из нескольких модулей. Чтобы Python видел каталог как пакет, кладут файл __init__.py. Он выполняется при первом импорте пакета и удобен, чтобы собрать публичный интерфейс в одном месте:
# shop/__init__.py
from .pricing import with_vat, apply_discount
Теперь снаружи пакет используется как единое целое:
# use_pkg.py
import shop
print(shop.with_vat(500))
print(shop.apply_discount(500, 25))
Вывод:
600.0
375.0
Оговорка по границе: с Python 3.3 работают и «неявные» пакеты без __init__.py (namespace packages), но обычный файл __init__.py остается стандартным способом объявить пакет и задать его интерфейс.
Частая ошибка при импорте
Импортируя имена по одному, легко опечататься. Пусть в pricing.py функция называется apply_discount, а в импорте написано apply_tax:
# badmain.py
from pricing import with_vat, apply_tax
print(with_vat(1000))
Запуск падает еще до первой строки логики:
ImportError: cannot import name 'apply_tax' from 'pricing'
Python сообщает не «нет модуля», а «нет такого имени В модуле»: сам файл pricing.py найден, но имени apply_tax в нем нет. Исправление — импортировать существующее имя (apply_discount) или добавить функцию в модуль. Отличайте это от ModuleNotFoundError, который означает, что не найден сам файл или пакет.
Выводы
- Модульное программирование — это принцип разбиения программы на части с одной ответственностью, общающиеся через узкий интерфейс.
- Интерфейс (что видно снаружи) и реализация (как устроено внутри) — разные вещи; менять реализацию можно, пока стабилен интерфейс.
- Связность (cohesion) хотим высокую внутри модуля, связанность (coupling) — низкую между модулями; это разные метрики, не путать.
- В Python модуль — это файл
.py, пакет — каталог с__init__.py; подключение идет черезimport. - Модульность окупается при разумных границах; чрезмерное дробление добавляет лишних связей.
Где применяется / связь с практикой
Модульность лежит в основе почти любого реального проекта: от разбиения скрипта на файлы до организации пакетов и импортов в командной кодовой базе. Начинающему важно набить руку именно на Python: научиться выносить логику в модули, собирать пакеты и держать связанность низкой — это база, с которой потом читаются и чужие проекты, и фреймворки.
Освойте тему на практике
Разобрать эти основы по шагам и на практике помогает курс Python Basic: синтаксис, функции, модули и пакеты. Посмотреть формат занятий и темы вживую можно на открытых уроках Otus — они бесплатные и проходят регулярно.
FAQ
Модуль, пакет и библиотека — это одно и то же?
Нет. Модуль — один файл .py, пакет — каталог модулей с __init__.py, а библиотека — это уже распространяемый набор пакетов и модулей, который ставят и подключают в разные проекты.
Чем модуль отличается от микросервиса?
Модуль работает внутри одного процесса программы и не запускается и не масштабируется отдельно. Микросервис — самостоятельно развернутый сервис, который живет и обновляется независимо и общается по сети.
В каких языках есть модульность?
Принцип поддерживают многие языки: Python, Ruby, Java (пакеты и модули), Rust, Go и другие. Это подход к организации кода, а не особенность одного языка, поэтому навык переносится между ними.



