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

Как реализовать использование объектов после десериализации?

2.3 Middle🔥 161 комментариев
#Python Core#REST API и HTTP

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

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

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

Использование объектов после десериализации

Десериализация (парсинг) данных из JSON, YAML или других форматов в объекты Python и их последующее использование — частая задача. Я использовал несколько подходов, каждый с плюсами и минусами.

1. Pydantic v2 — стандарт для валидации

Pydantic — это мощная библиотека для валидации и сериализации данных. Я использую её в большинстве проектов.

from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime

# Определяем модель
class User(BaseModel):
    id: int
    name: str
    email: str = Field(..., description="Email адрес")
    age: Optional[int] = None
    is_active: bool = True
    created_at: datetime = Field(default_factory=datetime.now)
    
    # Валидаторы
    @validator('name')
    def name_must_not_be_empty(cls, v):
        if not v or not v.strip():
            raise ValueError('Name cannot be empty')
        return v.strip()
    
    @validator('age')
    def age_must_be_positive(cls, v):
        if v is not None and v < 0:
            raise ValueError('Age must be positive')
        return v

# Десериализация из JSON
json_data = {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30
}

user = User(**json_data)  # Парсинг и валидация
print(user.name)  # Alice
print(user.is_active)  # True (значение по умолчанию)

# Доступ к данным
print(user.model_dump())  # Словарь
print(user.model_dump_json())  # JSON строка
print(user.model_json_schema())  # JSON Schema

2. Работа с вложенными объектами

from pydantic import BaseModel
from typing import List

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Company(BaseModel):
    name: str
    industry: str

class UserWithAddress(BaseModel):
    id: int
    name: str
    address: Address  # Вложенный объект
    company: Company
    friends: List[str] = []

# Десериализация с вложенными данными
data = {
    "id": 1,
    "name": "Alice",
    "address": {
        "street": "123 Main St",
        "city": "New York",
        "zip_code": "10001"
    },
    "company": {
        "name": "TechCorp",
        "industry": "Software"
    },
    "friends": ["Bob", "Charlie"]
}

user = UserWithAddress(**data)
print(user.address.city)  # New York
print(user.company.name)  # TechCorp
print(user.friends)  # ['Bob', 'Charlie']

3. Использование .model_validate() для безопасного парсинга

from pydantic import BaseModel, ValidationError

class Product(BaseModel):
    id: int
    name: str
    price: float

# Способ 1: Через конструктор (выбросит исключение при ошибке)
try:
    product = Product(**json_data)
except ValidationError as e:
    print(f"Validation error: {e}")

# Способ 2: Через .model_validate() (рекомендуется)
try:
    product = Product.model_validate(json_data)
except ValidationError as e:
    print(f"Invalid data: {e.json()}")

# Способ 3: Безопасно с обработкой ошибок
def parse_product(data: dict) -> Product | None:
    try:
        return Product.model_validate(data)
    except ValidationError as e:
        print(f"Failed to parse product: {e.errors()}")
        return None

product = parse_product(json_data)
if product:
    print(f"Product: {product.name} - ${product.price}")

4. Пользовательская логика после десериализации

Иногда нужно выполнить дополнительную логику после парсинга:

from pydantic import BaseModel, field_validator, model_validator
from datetime import datetime, timezone

class UserProfile(BaseModel):
    username: str
    password: str
    email: str
    created_at: datetime
    
    @field_validator('password')
    @classmethod
    def password_must_be_strong(cls, v):
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        return v
    
    @field_validator('email')
    @classmethod
    def email_must_be_valid(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email')
        return v
    
    @model_validator(mode='after')
    def check_user_consistency(self):
        """Проверка после создания объекта."""
        if self.created_at.tzinfo is None:
            # Если нет timezone info, добавляем UTC
            self.created_at = self.created_at.replace(tzinfo=timezone.utc)
        return self

# Использование
data = {
    "username": "alice",
    "password": "SecurePass123",
    "email": "alice@example.com",
    "created_at": "2024-01-01T10:00:00"
}

user = UserProfile(**data)
print(user.created_at)  # С timezone.utc

5. Методы объекта после десериализации

from pydantic import BaseModel
from typing import List

class Order(BaseModel):
    id: int
    items: List[dict]
    total_price: float
    
    def get_item_count(self) -> int:
        """Возвращает количество позиций."""
        return len(self.items)
    
    def apply_discount(self, percent: float) -> float:
        """Применяет скидку и возвращает новую цену."""
        return self.total_price * (1 - percent / 100)
    
    def is_expensive(self, threshold: float = 100.0) -> bool:
        """Проверяет дорогой ли заказ."""
        return self.total_price > threshold

# Десериализация и использование
order_data = {
    "id": 1,
    "items": [{"name": "Item 1", "price": 50}, {"name": "Item 2", "price": 75}],
    "total_price": 125.0
}

order = Order(**order_data)
print(f"Item count: {order.get_item_count()}")  # 2
print(f"With 10% discount: ${order.apply_discount(10)}")  # 112.5
print(f"Is expensive: {order.is_expensive(100)}")  # True

6. Работа с несколькими форматами данных

import json
import yaml
from pydantic import BaseModel

class Config(BaseModel):
    database_url: str
    api_key: str
    debug: bool = False

# Из JSON
json_str = '{"database_url": "postgres://localhost", "api_key": "secret123"}'
config = Config.model_validate_json(json_str)

# Из YAML
yaml_str = """
database_url: postgres://localhost
api_key: secret123
debug: true
"""
data = yaml.safe_load(yaml_str)
config = Config(**data)

# Из файла
def load_config(filename: str) -> Config:
    with open(filename, 'r') as f:
        if filename.endswith('.json'):
            return Config.model_validate_json(f.read())
        elif filename.endswith('.yaml'):
            return Config(**yaml.safe_load(f))
        else:
            raise ValueError(f"Unsupported format: {filename}")

config = load_config('config.json')
print(config.database_url)

7. Трансформация данных при десериализации

from pydantic import BaseModel, field_validator
from datetime import datetime
from pytz import timezone as tz

class Event(BaseModel):
    name: str
    start_time: datetime
    duration_hours: int
    
    @field_validator('name')
    @classmethod
    def normalize_name(cls, v):
        """Нормализует название события."""
        return v.strip().title()
    
    @field_validator('start_time', mode='before')
    @classmethod
    def parse_datetime(cls, v):
        """Парсит datetime из разных форматов."""
        if isinstance(v, str):
            # Пробуем несколько форматов
            for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d", "%d.%m.%Y"]:
                try:
                    return datetime.strptime(v, fmt)
                except ValueError:
                    continue
            raise ValueError(f"Cannot parse datetime: {v}")
        return v
    
    def get_end_time(self) -> datetime:
        """Возвращает время окончания события."""
        from datetime import timedelta
        return self.start_time + timedelta(hours=self.duration_hours)
    
    def is_today(self) -> bool:
        """Проверяет, сегодня ли событие."""
        today = datetime.now().date()
        return self.start_time.date() == today

# Использование
data = {
    "name": "python conference",
    "start_time": "2024-03-15 10:00:00",
    "duration_hours": 8
}

event = Event(**data)
print(event.name)  # Python Conference
print(event.get_end_time())  # 2024-03-15 18:00:00
print(event.is_today())  # False

8. Обработка ошибок десериализации

from pydantic import BaseModel, ValidationError
from typing import List

class Product(BaseModel):
    id: int
    name: str
    price: float

def parse_products(data_list: List[dict]) -> tuple[List[Product], List[dict]]:
    """Парсит список товаров, обрабатывая ошибки."""
    products = []
    errors = []
    
    for item in data_list:
        try:
            product = Product.model_validate(item)
            products.append(product)
        except ValidationError as e:
            errors.append({
                "item": item,
                "errors": e.errors()
            })
    
    return products, errors

# Использование
data = [
    {"id": 1, "name": "Product 1", "price": 100.0},
    {"id": "two", "name": "Product 2", "price": "cheap"},  # Ошибка
    {"id": 3, "name": "Product 3", "price": 150.0},
]

products, errors = parse_products(data)
print(f"Parsed: {len(products)} products")
print(f"Errors: {len(errors)} items")
for error in errors:
    print(f"Error in {error['item']}: {error['errors']}")

9. Практический пример из моего опыта

from pydantic import BaseModel, field_validator, ConfigDict
from typing import Optional
from datetime import datetime, timezone

class APIResponse(BaseModel):
    """Модель для ответов от внешнего API."""
    model_config = ConfigDict(from_attributes=True)  # Для ORM моделей
    
    id: int
    status: str
    data: Optional[dict] = None
    error: Optional[str] = None
    timestamp: datetime
    
    @field_validator('status')
    @classmethod
    def status_must_be_valid(cls, v):
        if v not in ['success', 'error', 'pending']:
            raise ValueError(f'Invalid status: {v}')
        return v
    
    def is_success(self) -> bool:
        return self.status == 'success'
    
    def is_error(self) -> bool:
        return self.status == 'error'
    
    def get_data_or_raise(self) -> dict:
        if self.is_error():
            raise RuntimeError(f"API error: {self.error}")
        return self.data or {}

# Использование в сервисе
def process_api_response(raw_data: dict):
    try:
        response = APIResponse(**raw_data)
        if response.is_success():
            data = response.get_data_or_raise()
            # Обрабатываем успешный ответ
            return data
        else:
            # Логируем ошибку
            print(f"API error at {response.timestamp}: {response.error}")
            return None
    except ValidationError as e:
        print(f"Invalid response format: {e}")
        return None

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

  • Используй Pydantic для всех данных из внешних источников
  • Валидируй сразу после десериализации
  • Используй type hints в моделях
  • Обрабатывай ошибки ValidationError
  • Пишни методы для бизнес-логики объектов
  • Избегай .dict() (deprecated) — используй .model_dump()
  • Не полагайся на конструктор для сложной логики — используй @model_validator

В своих проектах я использую Pydantic для 100% данных из API, БД и файлов. Это предотвращает множество ошибок на production.