Создание универсальных моделей с типами-параметрами, переиспользование логики валидации.
Generics позволяют создавать универсальные модели, переиспользуемые для разных типов данных
Обобщённые модели используют TypeVar для параметризации:
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
data: T
status: str
message: str
# Использование с разными типами
str_response = Response[str](data="success", status="ok", message="Done")
int_response = Response[int](data=42, status="ok", message="Number")
print(str_response.data) # "success"
print(int_response.data) # 42from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
U = TypeVar('U')
class Pair(BaseModel, Generic[T, U]):
first: T
second: U
# Разные комбинации типов
pair1 = Pair[str, int](first="hello", second=42)
pair2 = Pair[float, str](first=3.14, second="pi")
pair3 = Pair[int, int](first=10, second=20)
print(pair1) # Pair(first='hello', second=42)from typing import Generic, TypeVar, List
from pydantic import BaseModel
T = TypeVar('T')
class Item(BaseModel):
name: str
price: float
class PaginatedResponse(BaseModel, Generic[T]):
items: list[T]
total: int
page: int
per_page: int
# Использование
response = PaginatedResponse[Item](
items=[
{"name": "Mouse", "price": 29.99},
{"name": "Keyboard", "price": 79.99}
],
total=100,
page=1,
per_page=10
)
print(response.items[0].name) # "Mouse"from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class BaseResponse(BaseModel, Generic[T]):
data: T
status: str = "success"
class DetailedResponse(BaseResponse[T]):
message: str
timestamp: str
# Использование
response = DetailedResponse[dict](
data={"key": "value"},
message="Operation completed",
timestamp="2026-03-18"
)
print(response)
# DetailedResponse(data={'key': 'value'}, status='success',
# message='Operation completed', timestamp='2026-03-18')Можно ограничить допустимые типы:
from typing import TypeVar
from pydantic import BaseModel
# T должен быть подтипом Number
class Number:
pass
class IntNumber(Number):
value: int
class FloatNumber(Number):
value: float
T = TypeVar('T', bound=Number)
class NumberResponse(BaseModel, Generic[T]):
number: T
unit: str
# OK
response1 = NumberResponse[IntNumber](
number=IntNumber(value=42),
unit="pieces"
)
# OK
response2 = NumberResponse[FloatNumber](
number=FloatNumber(value=3.14),
unit="radians"
)from typing import TypeVar
from pydantic import BaseModel
# T может быть только int или str
T = TypeVar('T', int, str)
class Container(BaseModel, Generic[T]):
value: T
# OK
c1 = Container[int](value=42)
c2 = Container[str](value="hello")
# Ошибка: float не в списке разрешённых типов
# c3 = Container[float](value=3.14) # TypeErrorfrom typing import Generic, TypeVar, Union
from pydantic import BaseModel
T = TypeVar('T')
class Result(BaseModel, Generic[T]):
success: bool
data: T | None
error: str | None
# Успешный результат
success = Result[int](
success=True,
data=42,
error=None
)
# Ошибка
failure = Result[str](
success=False,
data=None,
error="Something went wrong"
)from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
data: T
status: str
response = Response[list[int]](
data=[1, 2, 3],
status="ok"
)
# Сериализация
dumped = response.model_dump()
print(dumped)
# {'data': [1, 2, 3], 'status': 'ok'}
# JSON
json_str = response.model_dump_json()
print(json_str)
# '{"data":[1,2,3],"status":"ok"}'from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class Wrapper(BaseModel, Generic[T]):
value: T
# Валидация из dict
data = {"value": "hello"}
wrapper = Wrapper[str].model_validate(data)
print(wrapper.value) # "hello"
# Валидация с преобразованием
data = {"value": "42"}
wrapper = Wrapper[int].model_validate(data)
print(wrapper.value) # 42 (строка преобразована в int)from typing import Generic, TypeVar, Optional
from pydantic import BaseModel
from datetime import datetime
T = TypeVar('T')
class APIResponse(BaseModel, Generic[T]):
"""Универсальный ответ API"""
data: T
error: Optional[str] = None
timestamp: datetime = None
request_id: str
def model_post_init(self, __context):
if self.timestamp is None:
self.timestamp = datetime.now()
# Использование
response = APIResponse[dict](
data={"user_id": 1, "name": "Alice"},
request_id="req-123"
)from typing import Generic, TypeVar, List, Optional
from pydantic import BaseModel
from datetime import datetime
T = TypeVar('T', bound=BaseModel)
class Entity(BaseModel):
id: int
created_at: datetime
class Repository(BaseModel, Generic[T]):
"""Репозиторий для сущностей"""
entities: list[T]
total: int
def get_by_id(self, id: int) -> T | None:
for entity in self.entities:
if entity.id == id:
return entity
return None
def filter(self, **kwargs) -> list[T]:
result = []
for entity in self.entities:
match = True
for key, value in kwargs.items():
if getattr(entity, key, None) != value:
match = False
break
if match:
result.append(entity)
return result
class User(Entity):
name: str
email: str
repo = Repository[User](
entities=[
User(id=1, name="Alice", email="alice@example.com", created_at=datetime.now()),
User(id=2, name="Bob", email="bob@example.com", created_at=datetime.now())
],
total=2
)
alice = repo.get_by_id(1)
print(alice.name) # "Alice"from typing import Generic, TypeVar, Callable
from pydantic import BaseModel
T = TypeVar('T')
class Event(BaseModel, Generic[T]):
"""Событие с данными типа T"""
event_type: str
payload: T
handlers: list[str] = []
class EventData(BaseModel):
user_id: int
action: str
# Создание события
event = Event[EventData](
event_type="user_action",
payload={"user_id": 1, "action": "login"},
handlers=["logger", "analytics"]
)
print(event.payload.user_id) # 1Создайте обобщённую модель для кэширования данных:
from typing import Generic, TypeVar, Optional, List
from pydantic import BaseModel
from datetime import datetime, timedelta
T = TypeVar('T')
class CacheEntry(BaseModel, Generic[T]):
# value: T — закэшированное значение
# created_at: datetime — время создания
# ttl_seconds: int — время жизни в секундах
# tags: list[str] — теги для поиска
# Добавьте вычисляемое поле:
# is_expired: bool — истёк ли срок жизни
pass
class Cache(BaseModel, Generic[T]):
# entries: list[CacheEntry[T]]
# max_size: int = 100
# Добавьте методы:
# get(self, index: int) -> T | None
# get_valid(self) -> list[T]
# add(self, value: T, ttl_seconds: int, tags: list[str])
# clear_expired(self) -> int (возвращает количество удалённых)
pass
# Пример использования:
cache = Cache[str](entries=[], max_size=100)
cache.add(value="data1", ttl_seconds=3600, tags=["important"])
cache.add(value="data2", ttl_seconds=60, tags=["temp"])
valid_data = cache.get_valid()
print(valid_data) # ["data1", "data2"]В следующей теме изучим дискриминированные объединения для работы с полиморфными данными.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.