Создание полей, вычисляемых на лету, кэширование, зависимости между полями.
Вычисляемые поля позволяют добавлять в модель данные, которые вычисляются на лету при сериализации
@computed_field создаёт поле, значение которого вычисляется при сериализации:
from pydantic import BaseModel, computed_field
class Rectangle(BaseModel):
width: float
height: float
@computed_field
@property
def area(self) -> float:
return self.width * self.height
@computed_field
@property
def perimeter(self) -> float:
return 2 * (self.width + self.height)
rect = Rectangle(width=10, height=5)
# Доступ как к обычному атрибуту
print(rect.area) # 50.0
# Вычисляемые поля включаются в сериализацию
print(rect.model_dump())
# {'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 100.0}Важно:
По умолчанию вычисляемое поле вычисляется при каждом обращении. Для кэширования:
from functools import cached_property
from pydantic import BaseModel, computed_field
class Expensive(BaseModel):
value: int
@computed_field
@cached_property
def expensive_calculation(self) -> int:
# Дорогостоящее вычисление
import time
time.sleep(1)
return self.value ** 2
obj = Expensive(value=10)
# Первое вычисление (медленно)
print(obj.expensive_calculation) # 100
# Второе обращение (мгновенно, из кэша)
print(obj.expensive_calculation) # 100class Order(BaseModel):
price: float
quantity: int
discount: float = 0
@computed_field
@property
def subtotal(self) -> float:
return self.price * quantity
@computed_field
@property
def discount_amount(self) -> float:
return self.subtotal * self.discount
@computed_field
@property
def total(self) -> float:
return self.subtotal - self.discount_amount
order = Order(price=100, quantity=2, discount=0.1)
print(order.total) # 180.0 (200 - 20)class User(BaseModel):
password_hash: str
name: str
@computed_field(exclude=True)
@property
def is_admin(self) -> bool:
return self.name == "admin"
user = User(password_hash="...", name="alice")
# is_admin не включается в сериализацию
data = user.model_dump()
# {'password_hash': '...', 'name': 'alice'}class Product(BaseModel):
price: float
quantity: int
@computed_field(alias="totalValue")
@property
def total_value(self) -> float:
return self.price * self.quantity
product = Product(price=10, quantity=5)
# С алиасом
data = product.model_dump(by_alias=True)
# {'price': 10.0, 'quantity': 5, 'totalValue': 50.0}from datetime import datetime
class Event(BaseModel):
start: datetime
end: datetime
@computed_field(return_type=float)
@property
def duration_hours(self) -> float:
delta = self.end - self.start
return delta.total_seconds() / 3600from typing import Optional
class Employee(BaseModel):
name: str
salary: float
bonus: Optional[float] = None
@computed_field
@property
def total_compensation(self) -> float:
return self.salary + (self.bonus or 0)
@computed_field
@property
def has_bonus(self) -> bool:
return self.bonus is not None and self.bonus > 0
emp1 = Employee(name="Alice", salary=50000)
emp2 = Employee(name="Bob", salary=60000, bonus=10000)
print(emp1.total_compensation) # 50000
print(emp1.has_bonus) # False
print(emp2.total_compensation) # 60000 + 10000 = 70000
print(emp2.has_bonus) # Truefrom datetime import datetime, date
from typing import List
class Subscription(BaseModel):
plan: str # "basic", "pro", "enterprise"
start_date: date
end_date: date
features: list[str]
@computed_field
@property
def duration_days(self) -> int:
return (self.end_date - self.start_date).days
@computed_field
@property
def is_active(self) -> bool:
return date.today() <= self.end_date
@computed_field
@property
def features_count(self) -> int:
return len(self.features)
@computed_field
@property
def plan_tier(self) -> str:
tiers = {
"basic": "Tier 1",
"pro": "Tier 2",
"enterprise": "Tier 3"
}
return tiers.get(self.plan, "Unknown")
sub = Subscription(
plan="pro",
start_date=date(2026, 1, 1),
end_date=date(2026, 12, 31),
features=["api", "support", "analytics"]
)
print(sub.duration_days) # 364
print(sub.is_active) # True/False в зависимости от даты
print(sub.features_count) # 3
print(sub.plan_tier) # "Tier 2"Вычисляемые поля не участвуют в валидации — они вычисляются после:
class Data(BaseModel):
value: int
@computed_field
@property
def doubled(self) -> int:
return self.value * 2
# Валидация проходит только для value
data = Data(value="10") # строка преобразуется в int
print(data.doubled) # 20Создайте модель BankAccount с вычисляемыми полями:
from pydantic import BaseModel, Field, computed_field
from datetime import date
from typing import List
from decimal import Decimal
class Transaction(BaseModel):
amount: Decimal
description: str
date: date
is_debit: bool # True для расхода, False для дохода
class BankAccount(BaseModel):
account_number: str
owner: str
transactions: list[Transaction] = Field(default_factory=list)
# Добавьте вычисляемые поля:
# 1. balance — текущий баланс (доходы - расходы)
# 2. total_debits — сумма всех расходов
# 3. total_credits — сумма всех доходов
# 4. transaction_count — количество транзакций
# 5. is_overdrawn — True если баланс отрицательный
pass
# Пример:
account = BankAccount(
account_number="123456789",
owner="Alice",
transactions=[
Transaction(amount=Decimal("1000.00"), description="Salary",
date=date(2026, 1, 1), is_debit=False),
Transaction(amount=Decimal("200.00"), description="Rent",
date=date(2026, 1, 5), is_debit=True),
Transaction(amount=Decimal("50.00"), description="Groceries",
date=date(2026, 1, 10), is_debit=True)
]
)
print(account.balance) # 1000 - 200 - 50 = 750
print(account.total_debits) # 250
print(account.total_credits) # 1000
print(account.is_overdrawn) # FalseВ следующей теме изучим конфигурацию моделей через ConfigDict.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.