Перейти к содержанию

Советы по производительности

В большинстве случаев Pydantic не станет вашим узким местом, следуйте этому правилу только в том случае, если вы уверены, что это необходимо.

В общем, используйте model_validate_json() а не model_validate(json.loads(...))

На model_validate(json.loads(...)) JSON анализируется в Python, затем преобразуется в dict, а затем проверяется внутри. С другой стороны, model_validate_json() уже выполняет внутреннюю проверку.

Есть несколько случаев, когда model_validate(json.loads(...)) может быть быстрее. В частности, при использовании валидатора 'before' или 'wrap' в модели проверка может быть быстрее с помощью двухэтапного метода. Вы можете прочитать больше об этих особых случаях в этом обсуждении .

Многие улучшения производительности в настоящее время находятся в разработке для pydantic-core , как обсуждается здесь . Как только эти изменения будут объединены, мы должны оказаться в точке, где model_validate_json() всегда быстрее, чем model_validate(json.loads(...)) .

TypeAdapter создается один раз

Идея здесь состоит в том, чтобы избегать создания валидаторов и сериализаторов больше, чем необходимо. Каждый раз, когда создается экземпляр TypeAdapter , он создает новый валидатор и сериализатор. Если вы используете TypeAdapter в функции, он будет создаваться при каждом вызове функции. Вместо этого создайте его экземпляр один раз и используйте его повторно.

\=== "❌ Плохо"

```py
from typing import List

from pydantic import TypeAdapter


def my_func():
    adapter = TypeAdapter(List[int])
    # do something with adapter
```

\=== "✅ Хорошо"

```py
from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])

def my_func():
    ...
    # do something with adapter
```

Sequence против list или tupleMapping против dict

При использовании Sequence Pydantic вызывает isinstance(value, Sequence) чтобы проверить, является ли значение последовательностью. Кроме того, Pydantic попытается выполнить проверку на соответствие различным типам последовательностей, например list и tuple . Если вы знаете, что значением является list или tuple , используйте list или tuple вместо Sequence .

То же самое относится к Mapping и dict . Если вы знаете, что значением является dict , используйте dict вместо Mapping .

Не выполняйте проверку, когда в этом нет необходимости. Используйте Any , чтобы сохранить значение неизменным.

Если вам не нужно проверять значение, используйте Any , чтобы сохранить значение неизменным.

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    a: Any


model = Model(a=1)

Избегайте дополнительной информации через подклассы примитивов

\=== "Не делай этого"

```py
class CompletedStr(str):
    def __init__(self, s: str):
        self.s = s
        self.done = False
```

\=== «Сделай это»

```py
from pydantic import BaseModel


class CompletedModel(BaseModel):
    s: str
    done: bool = False
```

Используйте объединение тегов, а не объединение

Теговый союз (или дискриминируемый союз) — это объединение с полем, указывающим его тип.

from typing import Any

from typing_extensions import Literal

from pydantic import BaseModel, Field


class DivModel(BaseModel):
    el_type: Literal['div'] = 'div'
    class_name: str | None = None
    children: list[Any] | None = None


class SpanModel(BaseModel):
    el_type: Literal['span'] = 'span'
    class_name: str | None = None
    contents: str | None = None


class ButtonModel(BaseModel):
    el_type: Literal['button'] = 'button'
    class_name: str | None = None
    contents: str | None = None


class InputModel(BaseModel):
    el_type: Literal['input'] = 'input'
    class_name: str | None = None
    value: str | None = None


class Html(BaseModel):
    contents: DivModel | SpanModel | ButtonModel | InputModel = Field(
        discriminator='el_type'
    )

Более подробную информацию см. в разделе «Дискриминированные профсоюзы» .

Используйте TypedDict для вложенных моделей

Вместо использования вложенных моделей используйте TypedDict для определения структуры данных.

??? info «Сравнение производительности» С помощью простого теста TypedDict примерно в 2,5 раза быстрее, чем вложенные модели:

```py
from timeit import timeit

from typing_extensions import TypedDict

from pydantic import BaseModel, TypeAdapter


class A(TypedDict):
    a: str
    b: int


class TypedModel(TypedDict):
    a: A


class B(BaseModel):
    a: str
    b: int


class Model(BaseModel):
    b: B


ta = TypeAdapter(TypedModel)
result1 = timeit(
    lambda: ta.validate_python({'a': {'a': 'a', 'b': 2}}), number=10000
)
result2 = timeit(
    lambda: Model.model_validate({'b': {'a': 'a', 'b': 2}}), number=10000
)
print(result2 / result1)
```

Избегайте валидаторов переноса, если вы действительно заботитесь о производительности.

Валидаторы Wrap обычно работают медленнее, чем другие валидаторы. Это связано с тем, что они требуют, чтобы данные материализовались в Python во время проверки. Валидаторы-обертки могут быть невероятно полезны для сложной логики проверки, но если вы ищете максимальную производительность, вам следует их избегать.

Ранний сбой с FailFast

Начиная с версии 2.8+, вы можете применять аннотацию FailFast к типам последовательностей, чтобы обеспечить преждевременный сбой, если какой-либо элемент последовательности не проходит проверку. Если вы используете эту аннотацию, вы не получите ошибок проверки для остальных элементов последовательности, если один из них завершится неудачей, поэтому вы фактически жертвуете видимостью ради производительности.

from typing import List

from typing_extensions import Annotated

from pydantic import FailFast, TypeAdapter, ValidationError

ta = TypeAdapter(Annotated[List[bool], FailFast()])
try:
    ta.validate_python([True, 'invalid', False, 'also invalid'])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[bool]
    1
      Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='invalid', input_type=str]
    """

Подробнее о FailFast читайте здесь.


本文总阅读量