PydanticJuniorCoding

Как определить опциональные поля и значения по умолчанию в Pydantic-модели?

Поля с дефолтом задаются через = значение или Field(default=...), для изменяемых типов — Field(default_factory=list). Optional[T] = None делает поле необязательным и допускающим None; Optional[T] без дефолта — обязательное поле, принимающее None.

Опциональные поля и значения по умолчанию в Pydantic

Pydantic предоставляет несколько способов объявить поле необязательным или задать ему значение по умолчанию. Важно понимать разницу между «поле может быть None» и «поле может отсутствовать в данных».

Значение по умолчанию — простой литерал

from pydantic import BaseModel

class User(BaseModel):
    name: str
    role: str = "viewer"       # по умолчанию 'viewer'
    is_active: bool = True     # по умолчанию True
    score: int = 0

u = User(name="Ivan")
print(u.role)       # 'viewer'
print(u.is_active)  # True

Optional[T] — поле может быть None

Optional[str] — это синоним str | None. Поле принимает строку или None, но по умолчанию всё равно обязательно, если не задать = None.

from pydantic import BaseModel
from typing import Optional

class Profile(BaseModel):
    username: str
    bio: Optional[str] = None       # необязательное, по умолчанию None
    avatar_url: str | None = None   # то же самое, современный синтаксис

p = Profile(username="alice")
print(p.bio)        # None
print(p.avatar_url) # None

p2 = Profile(username="bob", bio="Python developer")
print(p2.bio)       # 'Python developer'

Field() — расширенная настройка

Функция Field() из pydantic позволяет задать дефолт вместе с дополнительными параметрами: описанием, ограничениями, примером.

from pydantic import BaseModel, Field
from typing import Optional

class Product(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)  # обязательное
    price: float = Field(gt=0, description="Price in USD")
    discount: Optional[float] = Field(None, ge=0, le=1, description="0.0–1.0")
    tags: list[str] = Field(default_factory=list)  # изменяемый дефолт

p = Product(name="Widget", price=9.99)
print(p.discount)  # None
print(p.tags)      # []

default_factory — для изменяемых значений по умолчанию

Никогда не используйте изменяемые объекты (список, словарь) как дефолт напрямую — каждый экземпляр должен получить свою копию. Для этого есть default_factory:

from pydantic import BaseModel, Field
from datetime import datetime

class Event(BaseModel):
    name: str
    tags: list[str] = Field(default_factory=list)
    metadata: dict[str, str] = Field(default_factory=dict)
    created_at: datetime = Field(default_factory=datetime.utcnow)

e1 = Event(name="Deploy")
e2 = Event(name="Rollback")
e1.tags.append("prod")
print(e2.tags)  # [] — отдельный список, не тот же объект

Поля без значений по умолчанию — обязательные

Поле без дефолта и без Optional обязательно. Попытка создать модель без него вызовет ValidationError:

from pydantic import BaseModel, ValidationError

class Order(BaseModel):
    id: int          # обязательное
    comment: str = ""  # необязательное

try:
    Order()  # нет id
except ValidationError as e:
    print(e)
    # 1 validation error for Order
    # id
    #   Field required

Разница Optional и Required с None

from pydantic import BaseModel
from typing import Optional

class A(BaseModel):
    x: Optional[str]        # обязательное, но принимает None — НУЖНО передать явно
    y: Optional[str] = None # необязательное, дефолт None

# A()         — ValidationError: x is required
# A(x=None)   — OK, x=None, y=None
# A(x="hi")   — OK, x='hi', y=None

Подводные камни

  • Optional без = None не делает поле необязательнымOptional[str] без дефолта всё равно требует передачи значения (пусть и None). Это частая ошибка.
  • Изменяемый дефолт без default_factorytags: list = [] вызовет ошибку Pydantic v2; используйте Field(default_factory=list).
  • None vs отсутствие ключа — Pydantic не различает «ключ есть, значение None» и «ключа нет» по умолчанию. Если нужно это различие, используйте model_config = ConfigDict(populate_by_name=True) и анализируйте model_fields_set.
  • model_fields_set — атрибут экземпляра, содержащий только те поля, которые были явно переданы при создании. Полезно для PATCH-запросов (частичное обновление).
  • Pydantic v1 vs v2 — в v1 Optional[str] автоматически добавлял = None; в v2 это поведение убрали. Миграция с v1 на v2 часто ломает модели именно на этом.
  • Ellipsis (...) как маркер обязательного поляField(...) явно помечает поле как required; это синтаксис для документирования, не добавляющий дефолт.
  • Производительность default_factory — фабрика вызывается при каждом создании экземпляра; если фабрика дорогая (например, сетевой запрос), это повлияет на производительность.

Common mistakes

  • Описывать optional fields defaults только как термин и не показывать механизм на минимальном примере.
  • Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
  • Не связывать поведение с официальным контрактом Pydantic и реальной эксплуатацией.

What the interviewer is testing

  • Объясняет optional fields defaults через последовательность действий, а не через набор ключевых слов.
  • Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.

Sources

Related topics