← Назад к вопросам

Как гарантировать контракт для функции в Python?

2.0 Middle🔥 61 комментариев
#Python Core#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Гарантирование контракта функции в Python

Контракт функции — это соглашение между вызывающим кодом и функцией о том, какие входные данные она ожидает и какой результат вернёт. Существует несколько подходов для гарантирования этого контракта.

1. Type Hints (Аннотации типов)

Это самый простой и рекомендуемый способ описания контракта:

def add(a: int, b: int) -> int:
    """Складывает два целых числа."""
    return a + b

# Использование
result = add(5, 3)  # Корректно
result = add("5", 3)  # Type checker выдаст ошибку

Type hints помогают IDE и линтерам выявить ошибки, но не гарантируют проверку в runtime.

2. Проверка типов в Runtime с Pydantic

Для фактической проверки типов во время выполнения используйте Pydantic:

from pydantic import BaseModel, validator
from typing import List

class AddRequest(BaseModel):
    a: int
    b: int
    
    @validator("a", "b")
    def check_positive(cls, v):
        if v < 0:
            raise ValueError("Числа должны быть положительными")
        return v

def add(request: AddRequest) -> int:
    return request.a + request.b

3. Декораторы для проверки контракта

Создайте свой декоратор для проверки типов:

from functools import wraps
from typing import Callable, Any

def contract(input_check: Callable, output_check: Callable):
    """Декоратор для проверки контракта функции."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for arg in args:
                if not input_check(arg):
                    raise ValueError(f"Ошибка входных данных: {arg}")
            
            result = func(*args, **kwargs)
            
            if not output_check(result):
                raise ValueError(f"Нарушен контракт выхода: {result}")
            
            return result
        return wrapper
    return decorator

@contract(
    input_check=lambda x: isinstance(x, int) and x >= 0,
    output_check=lambda x: isinstance(x, int) and x >= 0
)
def factorial(n: int) -> int:
    return 1 if n <= 1 else n * factorial(n - 1)

4. Assertions для проверки условий

Используйте assert для проверки логических условий:

def divide(a: float, b: float) -> float:
    """Делит a на b. Гарантирует, что b != 0."""
    assert b != 0, "Делитель не может быть нулём"
    assert isinstance(a, (int, float)), "a должно быть числом"
    assert isinstance(b, (int, float)), "b должно быть числом"
    
    result = a / b
    assert result >= 0, "Результат должен быть положительным"
    return result

5. Использование контрактного программирования с icontract

Библиотека icontract предоставляет декораторы для контрактного программирования:

from icontract import require, ensure

@require(lambda x: x >= 0, description="x должно быть неотрицательным")
@ensure(lambda result: result >= 0, description="результат должен быть неотрицательным")
def sqrt_function(x: float) -> float:
    """Вычисляет квадратный корень."""
    return x ** 0.5

6. TypeGuard для сложных проверок типов

Для проверки пользовательских типов используйте TypeGuard:

from typing import TypeGuard, Union

def is_positive_int(value: Union[int, str]) -> TypeGuard[int]:
    """Проверяет, что значение — положительное целое число."""
    return isinstance(value, int) and value > 0

def process_number(num: Union[int, str]) -> int:
    """Обрабатывает положительное целое число."""
    if is_positive_int(num):
        return num * 2
    raise TypeError(f"Ожидается положительное целое число, получено: {num}")

7. Лучшие практики

  • Type hints везде, особенно в публичных функциях
  • Pydantic для валидации входных данных в API
  • icontract для критичной бизнес-логики
  • Assertions для отладки и проверки инвариантов
  • Документация с примерами использования

Пример комплексного подхода

from pydantic import BaseModel, Field
from typing import List
from icontract import require

class User(BaseModel):
    name: str = Field(..., min_length=1)
    age: int = Field(..., ge=0, le=150)
    email: str

@require(lambda users: len(users) > 0)
def process_users(users: List[User]) -> int:
    """Обрабатывает список пользователей и возвращает количество."""
    return len(users)

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