Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Обобщённые модели: Generic и параметризация
generics_models

Обобщённые модели: Generic и параметризация

Создание универсальных моделей с типами-параметрами, переиспользование логики валидации.

Обобщённые модели: Generic и параметризация

Generics позволяют создавать универсальные модели, переиспользуемые для разных типов данных

#Базовое использование Generic

Обобщённые модели используют 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) # 42

#Несколько типов-параметров

from 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)

#Generics с вложенными моделями

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')

#Ограничения типов (TypeVar bounds)

Можно ограничить допустимые типы:

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" )

#Конкретные значения TypeVar (TypeVar constraints)

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) # TypeError

#Generics с Union

from 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)

#Практические примеры

#API Response Wrapper

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" )

#Repository Pattern

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"

#Event System

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

#Мини-проект: Система кэширования с Generics

Создайте обобщённую модель для кэширования данных:

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"]

Далее: Дискриминированные объединения: Annotated и Tag