Что такое программный контракт в Python?

Ответ

Программный контракт — это формальное соглашение об интерфейсе между компонентами системы (например, модулями, классами или функциями). Он определяет взаимные обязательства: что компонент ожидает на входе и что гарантирует на выходе.

Контракт обычно определяет:

  • Предусловия (Preconditions): Условия, которые должны быть истинны перед вызовом функции (например, типы и значения аргументов).
  • Постусловия (Postconditions): Условия, которые должны быть истинны после завершения работы функции (например, тип и свойства возвращаемого значения).
  • Инварианты (Invariants): Условия, которые остаются неизменными для объекта на протяжении его жизненного цикла.

Способы реализации в Python:

  1. Аннотации типов (Type Hints): Основной способ определения контракта по типам данных.
  2. Докстринги (Docstrings): Словесное описание логики, аргументов, возвращаемых значений и возможных исключений.
  3. Утверждения (Assertions): Проверки условий (assert), которые активны в режиме разработки и отключаются в продакшене.
  4. Абстрактные базовые классы (ABC): Для определения интерфейсов, которые должны реализовывать дочерние классы.

Пример функции с контрактом:

from typing import List

def calculate_average(numbers: List[float]) -> float:
    """
    Вычисляет среднее значение для списка чисел.

    Args:
        numbers: Список чисел для вычисления среднего.

    Returns:
        Среднее арифметическое значение.

    Raises:
        ValueError: Если список пуст.
    """
    # Предусловие: список не должен быть пустым
    if not numbers:
        raise ValueError("Список для вычисления среднего не может быть пустым")

    result = sum(numbers) / len(numbers)

    # Постусловие (неявное): результат является float
    return result

# Корректное использование
print(calculate_average([1.0, 2.0, 3.0, 6.0])) # Вывод: 3.0

# Нарушение контракта
# calculate_average([]) # Вызовет ValueError

Соблюдение контрактов делает код более надежным, предсказуемым и легким для отладки.

Ответ 18+ 🔞

Слушай, а вот эта вся ваша программная хуйня с контрактами — это ж, блядь, как договор с самим собой, только на языке кода! Представь, ты пишешь функцию, а она тебе: «Чувак, я тебе помогу, но только если ты мне подашь список чисел, а не пустую коробку из-под пива, иначе я тебя нахуй пошлю с ValueError!». Это и есть контракт, ёпта!

Вот смотри, как это работает, блядь:

Предусловия — это как твои требования к гостю перед входом в хату: «Сними обувь, не матерись при бабушке и принеси бутылку». В коде это: «Аргументы должны быть вот таких типов, список не должен быть пустым, иначе — пиздец».

Постусловия — это твои гарантии после того, как гость ушёл: «Я обещал, что после вечеринки в квартире будет чисто (возвращаемое значение float), и оно будет!».

Инварианты — это как твоё основное правило жизни, которое не меняется: «Холодильник всегда должен быть полным» (состояние объекта должно оставаться валидным).

А теперь, сука, как это в Питоне делается? Да проще пареной репы, блядь!

  1. Аннотации типов — это когда ты прямо в коде пишешь, что функция ждёт List[float], а не какую-нибудь случайную хуйню. Компилятор не ругается, но линтер и IDE тебя уже подозрительно так косят.
  2. Докстринги — это твоё словесное объяснение на человеческом языке, что функция делает, что принимает и что отдаёт. Типа памятка для себя будущего или для коллеги, который будет это читать через полгода и материться.
  3. Утверждения (assert) — это, блядь, стражники на входе. Они проверяют условия в режиме отладки. В продакшене их обычно выключают, так что надеяться только на них — это как надеяться, что замок на двери сработает, если его отключить.
  4. Абстрактные базовые классы (ABC) — это когда ты говоришь: «Все мои наследники должны уметь делать вот это и вот это!». А если не умеют — получают по ебалу от интерпретатора.

Вот, смотри, живой пример, чтобы не быть голословным, как эта мартышлюшка:

from typing import List

def calculate_average(numbers: List[float]) -> float:
    """
    Вычисляет среднее значение для списка чисел.

    Args:
        numbers: Список чисел для вычисления среднего.

    Returns:
        Среднее арифметическое значение.

    Raises:
        ValueError: Если список пуст.
    """
    # Предусловие: список не должен быть пустым
    if not numbers:
        raise ValueError("Список для вычисления среднего не может быть пустым")

    result = sum(numbers) / len(numbers)

    # Постусловие (неявное): результат является float
    return result

# Корректное использование
print(calculate_average([1.0, 2.0, 3.0, 6.0])) # Вывод: 3.0

# Нарушение контракта
# calculate_average([]) # Вызовет ValueError

Видишь? Всё честно. Функция как бы говорит: «Дай мне числа — я дам тебе среднее. Не дашь — получишь по ебалу с ошибкой». И код становится, блядь, предсказуемым! Ты знаешь, чего ждать. А когда всё предсказуемо — отлаживать в тысячу раз легче, не нужно гадать, что за хуйня сломалась на этот раз.

Короче, контракты — это не бюрократия, а способ сохранить свою же, блядь, психику при разработке. Пользуйся, не будь мудаком.