Skip to content

字段

API文档

pydantic.fields.Field

本节将介绍自定义 Pydantic 模型字段的可用机制: 默认值、JSON Schema 元数据、约束等。

为此,Field() 函数被大量使用,其行为方式与 标准库中用于数据类的 field() 函数相同:

from pydantic import BaseModel, Field


class Model(BaseModel):
    name: str = Field(frozen=True)

Note

即使 name 被分配了一个值,它仍然是必需的且没有默认值。如果你想 强调必须提供一个值,可以使用 省略号

class Model(BaseModel):
    name: str = Field(..., frozen=True)

但是,不鼓励使用这种方式,因为它与静态类型检查器的配合不佳。

注解模式

为了对模型字段应用约束或附加 Field() 函数,Pydantic 支持使用 Annotated 类型构造来将元数据附加到注解上:

from typing import Annotated

from pydantic import BaseModel, Field, WithJsonSchema


class Model(BaseModel):
    name: Annotated[str, Field(strict=True), WithJsonSchema({'extra': 'data'})]

就静态类型检查器而言,name 仍然被类型化为 str,但 Pydantic 利用 可用的元数据来添加验证逻辑、类型约束等。

使用这种模式有一些优点:

  • 使用 f: <type> = Field(...) 形式可能会令人困惑,并可能误导用户认为 f 有一个默认值,而实际上它仍然是必需的。
  • 你可以为字段提供任意数量的元数据元素。如上例所示, Field() 函数仅支持一组有限的约束/元数据, 在某些情况下你可能需要使用不同的 Pydantic 工具,例如 WithJsonSchema
  • 类型可以变得可重用(参见关于 自定义类型 的文档 使用此模式)。

但是,请注意,Field() 函数的某些参数(即 defaultdefault_factoryalias)会被静态类型检查器考虑以合成正确的 __init__ 方法。注解模式不被它们理解,因此你应该使用普通的 赋值形式代替。

Tip

注解模式也可用于向类型的特定部分添加元数据。例如, 验证约束 可以通过这种方式添加:

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    int_list: list[Annotated[int, Field(gt=0)]]
    # 有效: [1, 3]
    # 无效: [-1, 2]

注意不要混合使用字段类型元数据:

class Model(BaseModel):
    field_bad: Annotated[int, Field(deprecated=True)] | None = None  # (1)!
    field_ok: Annotated[int | None, Field(deprecated=True)] = None  # (2)!
  1. Field() 函数应用于 int 类型,因此 deprecated 标志不会产生任何效果。虽然这可能令人困惑,因为 Field() 函数的名称暗示它应该应用于字段, 但该 API 是在此函数是提供元数据的唯一方式时设计的。你可以 选择使用现在受 Pydantic 支持的 annotated_types 库。

  2. Field() 函数应用于“顶级”联合类型, 因此 deprecated 标志将应用于字段。

默认值

字段的默认值可以使用正常的赋值语法或通过向 default 参数提供值来提供:

from pydantic import BaseModel, Field


class User(BaseModel):
    # 两个字段都不是必需的:
    name: str = 'John Doe'
    age: int = Field(default=20)

Warning

在 Pydantic V1 中,类型注解为 Any 或被 Optional 包装的字段会被赋予隐式默认值 None,即使没有 明确指定默认值。在 Pydantic V2 中不再如此。

你也可以传递一个可调用对象给 default_factory 参数,该可调用对象将被调用来生成默认值:

from uuid import uuid4

from pydantic import BaseModel, Field


class User(BaseModel):
    id: str = Field(default_factory=lambda: uuid4().hex)

默认工厂也可以接受一个必需的参数,在这种情况下,已经验证的数据将作为字典传递。

from pydantic import BaseModel, EmailStr, Field


class User(BaseModel):
    email: EmailStr
    username: str = Field(default_factory=lambda data: data['email'])


user = User(email='user@example.com')
print(user.username)
#> user@example.com

data 参数将包含已经验证的数据,基于 模型字段的顺序 (如果 username 定义在 email 之前,上面的示例将失败)。

验证默认值

默认情况下,Pydantic 不会验证默认值。validate_default 字段参数 (或 validate_default 配置值)可用于 启用此行为:

from pydantic import BaseModel, Field, ValidationError


class User(BaseModel):
    age: int = Field(default='twelve', validate_default=True)


try:
    user = User()
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    age
      输入应为有效整数,无法将字符串解析为整数 [type=int_parsing, input_value='twelve', input_type=str]
    """

可变默认值

Python 中一个常见的错误来源是使用可变对象作为函数或方法参数的默认值, 因为相同的实例会在每次调用中被重复使用。

dataclasses 模块实际上在这种情况下会引发错误,指示你应该使用 默认工厂 代替。

虽然可以在 Pydantic 中做同样的事情,但这不是必需的。如果默认值不可哈希, Pydantic 将在创建模型的每个实例时创建默认值的深拷贝:

from pydantic import BaseModel


class Model(BaseModel):
    item_counts: list[dict[str, int]] = [{}]


m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]

m2 = Model()
print(m2.item_counts)
#> [{}]

字段别名

Tip

专用章节 中阅读更多关于别名的信息。

为了验证和序列化,你可以为字段定义别名。

有三种定义别名的方式:

  • Field(alias='foo')
  • Field(validation_alias='foo')
  • Field(serialization_alias='foo')

alias 参数用于验证序列化。如果你想分别使用 不同的别名进行验证和序列化,可以使用 validation_aliasserialization_alias 参数,它们将仅适用于各自的用例。

以下是使用 alias 参数的示例:

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(alias='username')


user = User(username='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'username': 'johndoe'}
  1. 别名 'username' 用于实例创建和验证。
  2. 我们使用 model_dump() 将模型转换为可序列化格式。

    注意 by_alias 关键字参数默认为 False,必须显式指定才能转储 使用字段(序列化)别名的模型。

    你也可以使用 ConfigDict.serialize_by_alias 来 在模型级别配置此行为。

    by_alias=True 时,使用序列化期间的别名 'username'

如果你只想为验证使用别名,可以使用 validation_alias 参数:

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(validation_alias='username')


user = User(username='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'name': 'johndoe'}
  1. 验证别名 'username' 在验证期间使用。
  2. 字段名称 'name' 在序列化期间使用。

如果你只想为序列化定义别名,可以使用 serialization_alias 参数:

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(serialization_alias='username')


user = User(name='johndoe')  # (1)!
print(user)
#> name='johndoe'
print(user.model_dump(by_alias=True))  # (2)!
#> {'username': 'johndoe'}
  1. 字段名称 'name' 用于验证。
  2. 序列化别名 'username' 用于序列化。

别名优先级和优先顺序

如果你同时使用 aliasvalidation_aliasserialization_aliasvalidation_alias 在验证时将优先于 alias,而 serialization_alias 在序列化时将优先 于 alias

如果你为 alias_generator 模型设置提供值,你可以通过 alias_priority 字段参数控制字段别名和生成别名的优先顺序。你可以在 这里 阅读更多关于别名优先级的信息。

静态类型检查/IDE 支持

如果你为 alias 字段参数提供值,静态类型检查器将使用此别名而不是 实际字段名称来合成 __init__ 方法:

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(alias='username')


user = User(username='johndoe')  # (1)!
  1. 被类型检查器接受。

这意味着当使用 validate_by_name 模型设置(允许在模型验证期间使用字段名称和别名)时,类型检查器会在使用实际字段名称时出错:

from pydantic import BaseModel, ConfigDict, Field


class User(BaseModel):
    model_config = ConfigDict(validate_by_name=True)

    name: str = Field(alias='username')


user = User(name='johndoe')  # (1)!
  1. 不被类型检查器接受。

如果你仍然希望类型检查器使用字段名称而不是别名,可以使用 注解模式 (仅被 Pydantic 理解):

from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field


class User(BaseModel):
    model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

    name: Annotated[str, Field(alias='username')]


user = User(name='johndoe')  # (1)!
user = User(username='johndoe')  # (2)!
  1. 被类型检查器接受。
  2. 不被类型检查器接受。

验证别名

尽管 Pydantic 在创建模型实例时对待 aliasvalidation_alias 相同,但类型检查器 仅理解 alias 字段参数。作为变通方法,你可以改为指定一个 aliasserialization_alias(与字段名称相同),因为 serialization_alias 将在序列化期间覆盖 alias

from pydantic import BaseModel, Field


class MyModel(BaseModel):
    my_field: int = Field(validation_alias='myValidationAlias')

改为:

from pydantic import BaseModel, Field


class MyModel(BaseModel):
    my_field: int = Field(
        alias='myValidationAlias',
        serialization_alias='my_field',
    )


m = MyModel(myValidationAlias=1)
print(m.model_dump(by_alias=True))
#> {'my_field': 1}

数值约束

有一些关键字参数可用于约束数值:

  • gt - 大于
  • lt - 小于
  • ge - 大于或等于
  • le - 小于或等于
  • multiple_of - 给定数字的倍数
  • allow_inf_nan - 允许 'inf''-inf''nan'

这是一个示例:

from pydantic import BaseModel, Field


class Foo(BaseModel):
    positive: int = Field(gt=0)
    non_negative: int = Field(ge=0)
    negative: int = Field(lt=0)
    non_positive: int = Field(le=0)
    even: int = Field(multiple_of=2)
    love_for_pydantic: float = Field(allow_inf_nan=True)


foo = Foo(
    positive=1,
    non_negative=0,
    negative=-1,
    non_positive=0,
    even=2,
    love_for_pydantic=float('inf'),
)
print(foo)
"""
positive=1 non_negative=0 negative=-1 non_positive=0 even=2 love_for_pydantic=inf
"""
JSON Schema

在生成的 JSON schema 中:

  • gtlt 约束将被转换为 exclusiveMinimumexclusiveMaximum
  • gele 约束将被转换为 minimummaximum
  • multiple_of 约束将被转换为 multipleOf

上面的代码片段将生成以下 JSON Schema:

{
  "title": "Foo",
  "type": "object",
  "properties": {
    "positive": {
      "title": "Positive",
      "type": "integer",
      "exclusiveMinimum": 0
    },
    "non_negative": {
      "title": "Non Negative",
      "type": "integer",
      "minimum": 0
    },
    "negative": {
      "title": "Negative",
      "type": "integer",
      "exclusiveMaximum": 0
    },
    "non_positive": {
      "title": "Non Positive",
      "type": "integer",
      "maximum": 0
    },
    "even": {
      "title": "Even",
      "type": "integer",
      "multipleOf": 2
    },
    "love_for_pydantic": {
      "title": "Love For Pydantic",
      "type": "number"
    }
  },
  "required": [
    "positive",
    "non_negative",
    "negative",
    "non_positive",
    "even",
    "love_for_pydantic"
  ]
}

有关更多详细信息,请参阅 [JSON Schema Draft 2020-12]。

复合类型的约束

如果你将字段约束与复合类型一起使用,在某些情况下可能会发生错误。为避免潜在问题, 你可以使用 Annotated

from typing import Annotated, Optional

from pydantic import BaseModel, Field


class Foo(BaseModel):
    positive: Optional[Annotated[int, Field(gt=0)]]
    # 在某些情况下可能出错,不推荐:
    non_negative: Optional[int] = Field(ge=0)

字符串约束

API 文档

pydantic.types.StringConstraints

有一些字段可用于约束字符串:

  • min_length:字符串的最小长度。
  • max_length:字符串的最大长度。
  • pattern:字符串必须匹配的正则表达式。

这是一个示例:

from pydantic import BaseModel, Field


class Foo(BaseModel):
    short: str = Field(min_length=3)
    long: str = Field(max_length=10)
    regex: str = Field(pattern=r'^\d*$')  # (1)!


foo = Foo(short='foo', long='foobarbaz', regex='123')
print(foo)
#> short='foo' long='foobarbaz' regex='123'
  1. 只允许数字。
JSON Schema

在生成的 JSON schema 中:

  • min_length 约束将被转换为 minLength
  • max_length 约束将被转换为 maxLength
  • pattern 约束将被转换为 pattern

上面的代码片段将生成以下 JSON Schema:

{
  "title": "Foo",
  "type": "object",
  "properties": {
    "short": {
      "title": "Short",
      "type": "string",
      "minLength": 
    },
    "long": {
      "title": "Long",
      "type": "string",
      "maxLength": 10
    },
    "regex": {
      "title": "Regex",
      "type": "string",
      "pattern": "^\\d*$"
    }
  },
  "required": [
    "short",
    "long",
    "regex"
  ]
}

小数约束

有一些字段可用于约束小数:

  • max_digitsDecimal 中的最大位数。不包括小数点前的零 或尾随的小数零。
  • decimal_places:允许的最大小数位数。不包括尾随的小数零。

这是一个示例:

from decimal import Decimal

from pydantic import BaseModel, Field


class Foo(BaseModel):
    precise: Decimal = Field(max_digits=5, decimal_places=2)


foo = Foo(precise=Decimal('123.45'))
print(foo)
#> precise=Decimal('123.45')

数据类约束

有一些字段可用于约束数据类:

  • init:字段是否应包含在数据类的 __init__ 中。
  • init_var:字段是否应被视为数据类中的 [仅初始化字段]。
  • kw_only:字段是否应为数据类构造函数中的仅关键字参数。

这是一个示例:

from pydantic import BaseModel, Field
from pydantic.dataclasses import dataclass


@dataclass
class Foo:
    bar: str
    baz: str = Field(init_var=True)
    qux: str = Field(kw_only=True)


class Model(BaseModel):
    foo: Foo


model = Model(foo=Foo('bar', baz='baz', qux='qux'))
print(model.model_dump())  # (1)!
#> {'foo': {'bar': 'bar', 'qux': 'qux'}}
  1. baz 字段不包含在 model_dump() 输出中,因为它是一个仅初始化字段。

字段表示

参数 repr 可用于控制字段是否应包含在模型的字符串 表示中。

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(repr=True)  # (1)!
    age: int = Field(repr=False)


user = User(name='John', age=42)
print(user)
#> name='John'
  1. 这是默认值。

鉴别器

参数 discriminator 可用于控制将用于区分联合中不同 模型的字段。它接受字段名称或 Discriminator 实例。Discriminator 方法在鉴别器字段对于联合中的所有模型不完全相同时非常有用。

以下示例显示如何使用带有字段名称的 discriminator

from typing import Literal, Union

from pydantic import BaseModel, Field


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    age: int


class Model(BaseModel):
    pet: Union[Cat, Dog] = Field(discriminator='pet_type')


print(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}}))  # (1)!
#> pet=Cat(pet_type='cat', age=12)
  1. 有关 [验证数据] 的更多信息,请参阅 [模型] 页面。
from typing import Literal

from pydantic import BaseModel, Field


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    age: int


class Model(BaseModel):
    pet: Cat | Dog = Field(discriminator='pet_type')


print(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}}))  # (1)!
#> pet=Cat(pet_type='cat', age=12)
  1. 有关 [验证数据] 的更多信息,请参阅 [模型] 页面。

以下示例显示如何使用带有 Discriminator 实例的 discriminator 关键字参数:

from typing import Annotated, Literal, Union


from pydantic import BaseModel, Discriminator, Field, Tag


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_kind: Literal['dog']
    age: int


def pet_discriminator(v):
    if isinstance(v, dict):
        return v.get('pet_type', v.get('pet_kind'))
    return getattr(v, 'pet_type', getattr(v, 'pet_kind', None))


class Model(BaseModel):
    pet: Union[Annotated[Cat, Tag('cat')], Annotated[Dog, Tag('dog')]] = Field(
        discriminator=Discriminator(pet_discriminator)
    )


print(repr(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}})))
#> Model(pet=Cat(pet_type='cat', age=12))

print(repr(Model.model_validate({'pet': {'pet_kind': 'dog', 'age': 12}})))
#> Model(pet=Dog(pet_kind='dog', age=12))
from typing import Annotated, Literal


from pydantic import BaseModel, Discriminator, Field, Tag


class Cat(BaseModel):
    pet_type: Literal['cat']
    age: int


class Dog(BaseModel):
    pet_kind: Literal['dog']
    age: int


def pet_discriminator(v):
    if isinstance(v, dict):
        return v.get('pet_type', v.get('pet_kind'))
    return getattr(v, 'pet_type', getattr(v, 'pet_kind', None))


class Model(BaseModel):
    pet: Annotated[Cat, Tag('cat')] | Annotated[Dog, Tag('dog')] = Field(
        discriminator=Discriminator(pet_discriminator)
    )


print(repr(Model.model_validate({'pet': {'pet_type': 'cat', 'age': 12}})))
#> Model(pet=Cat(pet_type='cat', age=12))

print(repr(Model.model_validate({'pet': {'pet_kind': 'dog', 'age': 12}})))
#> Model(pet=Dog(pet_kind='dog', age=12))

你也可以利用 Annotated 来定义你的鉴别联合。 有关更多详细信息,请参阅 [鉴别联合] 文档。

严格模式

Field 上的 strict 参数指定是否应在“严格模式”下验证字段。 在严格模式下,Pydantic 会在验证期间抛出错误,而不是在 strict=True 的字段上强制转换数据。

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str = Field(strict=True)
    age: int = Field(strict=False)  # (1)!


user = User(name='John', age='42')  # (2)!
print(user)
#> name='John' age=42
  1. 这是默认值。
  2. age 字段未在严格模式下验证。因此,它可以被分配一个字符串。

有关更多详细信息,请参阅 严格模式

有关 Pydantic 在严格和宽松模式下如何转换数据的更多详细信息,请参阅 转换表

不可变性

参数 frozen 用于模拟冻结数据类的行为。它用于防止字段在 模型创建后被分配新值(不可变性)。

有关更多详细信息,请参阅 [冻结数据类文档]。

from pydantic import BaseModel, Field, ValidationError


class User(BaseModel):
    name: str = Field(frozen=True)
    age: int


user = User(name='John', age=42)

try:
    user.name = 'Jane'  # (1)!
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    name
      字段已冻结 [type=frozen_field, input_value='Jane', input_type=str]
    """
  1. 由于 name 字段已冻结,不允许赋值。

排除

exclude 参数可用于控制在导出模型时应从模型中排除哪些字段。

参见以下示例:

from pydantic import BaseModel, Field


class User(BaseModel):
    name: str
    age: int = Field(exclude=True)


user = User(name='John', age=42)
print(user.model_dump())  # (1)!
#> {'name': 'John'}
  1. age 字段不包含在 model_dump() 输出中,因为它被排除了。

有关更多详细信息,请参阅 [序列化] 部分。

已弃用的字段

deprecated 参数可用于将字段标记为已弃用。这样做将导致:

  • 访问字段时发出运行时弃用警告。
  • 生成的 JSON schema 中设置 deprecated 关键字。

此参数接受不同的类型,如下所述。

deprecated 作为字符串

该值将用作弃用消息。

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, Field(deprecated='This is deprecated')]


print(Model.model_json_schema()['properties']['deprecated_field'])
#> {'deprecated': True, 'title': 'Deprecated Field', 'type': 'integer'}

通过 @warnings.deprecated 装饰器进行 deprecated

@warnings.deprecated 装饰器(或 Python 3.12 及更低版本上的 typing_extensions 回溯)可以用作实例。

from typing import Annotated

from typing_extensions import deprecated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, deprecated('This is deprecated')]

    # 或显式使用 `Field`:
    alt_form: Annotated[int, Field(deprecated=deprecated('This is deprecated'))]
from typing import Annotated
from warnings import deprecated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, deprecated('This is deprecated')]

    # 或显式使用 `Field`:
    alt_form: Annotated[int, Field(deprecated=deprecated('This is deprecated'))]

categorystacklevel 的支持

此功能的当前实现不考虑 deprecated 装饰器的 categorystacklevel 参数。这可能会在未来的 Pydantic 版本中实现。

deprecated 作为布尔值

from typing import Annotated

from pydantic import BaseModel, Field


class Model(BaseModel):
    deprecated_field: Annotated[int, Field(deprecated=True)]


print(Model.model_json_schema()['properties']['deprecated_field'])
#> {'deprecated': True, 'title': 'Deprecated Field', 'type': 'integer'}

在验证器中访问已弃用的字段

在验证器内部访问已弃用的字段时,将发出弃用警告。你可以使用 catch_warnings 来显式忽略它:

import warnings

from typing_extensions import Self

from pydantic import BaseModel, Field, model_validator


class Model(BaseModel):
    deprecated_field: int = Field(deprecated='This is deprecated')

    @model_validator(mode='after')
    def validate_model(self) -> Self:
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            self.deprecated_field = self.deprecated_field * 2

自定义 JSON Schema

一些字段参数专门用于自定义生成的 JSON schema。相关的参数是:

  • title
  • description
  • examples
  • json_schema_extra

有关使用字段自定义/修改 JSON schema 的更多信息,请参阅 JSON schema 文档中的 [自定义 JSON Schema] 部分。

computed_field 装饰器

API 文档

computed_field

computed_field 装饰器可用于在序列化模型或数据类时包含 propertycached_property 属性。 该属性也将在 JSON Schema(在序列化模式下)中被考虑。

Note

属性对于从其他字段计算的字段,或对于 计算成本高昂的字段(因此,如果使用 cached_property 则被缓存)非常有用。

但是,请注意 Pydantic 不会对包装的属性执行任何额外的逻辑 (验证、缓存无效等)。

这是一个带有计算字段的模型的 JSON schema(在序列化模式下)生成的示例:

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property  # (1)!
    def volume(self) -> float:
        return self.width * self.height * self.depth


print(Box.model_json_schema(mode='serialization'))
"""
{
    'properties': {
        'width': {'title': 'Width', 'type': 'number'},
        'height': {'title': 'Height', 'type': 'number'},
        'depth': {'title': 'Depth', 'type': 'number'},
        'volume': {'readOnly': True, 'title': 'Volume', 'type': 'number'},
    },
    'required': ['width', 'height', 'depth', 'volume'],
    'title': 'Box',
    'type': 'object',
}
"""
  1. 如果未指定,computed_field 将隐式转换方法 为 property。但是,为了类型检查目的,最好显式使用 @property 装饰器。

这是一个使用带有计算字段的 model_dump 方法的示例:

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property
    def volume(self) -> float:
        return self.width * self.height * self.depth


b = Box(width=1, height=2, depth=3)
print(b.model_dump())
#> {'width': 1.0, 'height': 2.0, 'depth': 3.0, 'volume': 6.0}

与常规字段一样,计算字段可以标记为已弃用:

from typing_extensions import deprecated

from pydantic import BaseModel, computed_field


class Box(BaseModel):
    width: float
    height: float
    depth: float

    @computed_field
    @property
    @deprecated("'volume' is deprecated")
    def volume(self) -> float:
        return self.width * self.height * self.depth