Как гарантировать контракт для функции в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Гарантирование контракта функции в 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)
Этот комплексный подход гарантирует, что функция работает корректно и соответствует своему контракту.