← Назад к вопросам
Что такое контракт при взаимодействии модулей в Python?
1.7 Middle🔥 111 комментариев
#Другое
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое контракт при взаимодействии модулей в Python?
Контракт модуля — это неявное или явное соглашение между частями программы о том, как они должны взаимодействовать. Контракт определяет ожидания обеих сторон: что одна сторона должна предоставить, а другая обещает правильно использовать. Это концепция из дизайна по контракту (Design by Contract).
Элементы контракта
- Предусловие (Precondition) — что должно быть истинно перед вызовом
- Постусловие (Postcondition) — что гарантированно будет истинно после вызова
- Инвариант класса (Class Invariant) — что должно оставаться истинным всегда
Простой пример контракта
class BankAccount:
def __init__(self, balance: float):
# Инвариант: баланс всегда >= 0
if balance < 0:
raise ValueError("Баланс не может быть отрицательным")
self._balance = balance
def withdraw(self, amount: float) -> float:
# Предусловие: сумма положительна и <= баланса
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
if amount > self._balance:
raise ValueError("Недостаточно средств")
# Выполняем операцию
self._balance -= amount
# Постусловие: баланс уменьшился на переданную сумму
return amount
def deposit(self, amount: float):
# Предусловие
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
old_balance = self._balance
self._balance += amount
# Постусловие: баланс увеличился
assert self._balance == old_balance + amount
Контракт при использовании типов (Type Hints)
Типизация — это форма контракта: функция обещает возвращать определённый тип.
from typing import List, Optional
def find_user(user_id: int) -> Optional[dict]:
"""
Контракт:
- Принимает целое число user_id
- Возвращает словарь с данными или None
Предусловие: user_id > 0
Постусловие: результат либо dict, либо None
"""
if user_id <= 0:
raise ValueError("ID должен быть положительным")
# ...
return {"id": user_id, "name": "John"} # или None
Явный контракт через документацию
class DataValidator:
def validate_email(self, email: str) -> bool:
"""
Проверяет корректность email.
Контракт:
- Принимает строку (не None)
- Возвращает True если email валидный, False иначе
- Не выбрасывает исключения для некорректного формата
- Работает с email от 5 до 254 символов
Args:
email: строка с email адресом
Returns:
True если email валидный
Raises:
TypeError: если email не строка
"""
if not isinstance(email, str):
raise TypeError("Email должен быть строкой")
if len(email) < 5 or len(email) > 254:
return False
return "@" in email and "." in email
Контракт между модулями
Когда модуль A использует модуль B:
# module_b.py
class PaymentProcessor:
def process_payment(self, amount: float, card: dict) -> bool:
"""
Контракт модуля B:
Предусловие (требования к модулю A):
- amount > 0
- card содержит ключи: number, cvv, expiry
- card[number] — 16 цифр
Постусловие (гарантии модуля B):
- Возвращает True если платёж успешен
- Никогда не выбросит исключение для валидного ввода
- Логирует все попытки платежа
"""
# Проверяем предусловия
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
required_keys = {"number", "cvv", "expiry"}
if not required_keys.issubset(card.keys()):
raise ValueError(f"Карта должна содержать: {required_keys}")
# Обработка платежа
result = self._send_to_gateway(amount, card)
return result
# module_a.py (использует module_b)
class OrderService:
def __init__(self, payment_processor: PaymentProcessor):
self.payment_processor = payment_processor
def complete_order(self, order: dict) -> bool:
"""
Пример контракта использования:
- Вызывает payment_processor только с валидными данными
- Полагается на то, что процессор гарантирует безопасность
- Не ловит исключения от процессора (они — признак нарушения контракта)
"""
amount = order["total"]
card = order["payment_card"]
# Модуль A соблюдает контракт: передаёт валидные данные
if amount <= 0 or not card.get("number"):
raise ValueError("Невалидный заказ") # Проверяем перед вызовом
# Теперь можем безопасно вызвать payment_processor
success = self.payment_processor.process_payment(amount, card)
return success
Проверка контракта (Assertions)
def divide(a: int, b: int) -> float:
# Предусловие: b не должно быть 0
assert b != 0, "Делитель не может быть нулём"
result = a / b
# Постусловие: результат корректен
assert abs(result * b - a) < 0.0001, "Ошибка расчёта"
return result
Нарушение контракта
# Нарушение контракта — модуль A не соблюдает предусловия
processor = PaymentProcessor()
processor.process_payment(-100, {}) # Предусловие нарушено!
# Соблюдение контракта — модуль A проверяет данные
if order["amount"] > 0 and valid_card(order["card"]):
processor.process_payment(order["amount"], order["card"])
Зачем нужны контракты?
- Ясность — четко определены ожидания
- Надёжность — каждый модуль знает границы ответственности
- Отладка — легче найти, кто нарушил контракт
- Тестирование — проверяем соблюдение контракта
- Рефакторинг — контракт остаётся, реализация может меняться
Вывод: Контракт при взаимодействии модулей — это соглашение о предусловиях, постусловиях и инвариантах. В Python это выражается через типизацию, документацию и явные проверки. Правильно определённые контракты делают код надёжнее и понятнее.