Aller au contenu

Conseils de performances

Dans la plupart des cas, Pydantic ne sera pas votre goulot d'étranglement, suivez-le uniquement si vous êtes sûr que c'est nécessaire.

En général, utilisez model_validate_json() et non model_validate(json.loads(...))

Sur model_validate(json.loads(...)) , le JSON est analysé en Python, puis converti en dict, puis validé en interne. D'un autre côté, model_validate_json() effectue déjà la validation en interne.

Il existe quelques cas où model_validate(json.loads(...)) peut être plus rapide. Plus précisément, lors de l'utilisation d'un validateur 'before' ou 'wrap' sur un modèle, la validation peut être plus rapide avec la méthode en deux étapes. Vous pouvez en savoir plus sur ces cas particuliers dans cette discussion .

De nombreuses améliorations de performances sont actuellement en cours pour pydantic-core , comme indiqué ici . Une fois ces modifications fusionnées, nous devrions être au point où model_validate_json() est toujours plus rapide que model_validate(json.loads(...)) .

TypeAdapter instancié une fois

L’idée ici est d’éviter de construire des validateurs et des sérialiseurs plus que nécessaire. Chaque fois qu'un TypeAdapter est instancié, il construira un nouveau validateur et sérialiseur. Si vous utilisez un TypeAdapter dans une fonction, il sera instancié à chaque appel de la fonction. Au lieu de cela, instanciez-le une fois et réutilisez-le.

\=== "❌Mauvais"

```py
from typing import List

from pydantic import TypeAdapter


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

\=== "✅Bien"

```py
from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])

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

Sequence vs list ou tuple - Mapping vs dict

Lors de l'utilisation Sequence , Pydantic appelle isinstance(value, Sequence) pour vérifier si la valeur est une séquence. De plus, Pydantic tentera de valider différents types de séquences, comme list et tuple . Si vous savez que la valeur est une list ou tuple , utilisez list ou tuple au lieu de Sequence .

La même chose s'applique à Mapping et dict . Si vous savez que la valeur est un dict , utilisez dict au lieu de Mapping .

N'effectuez pas de validation lorsque vous n'êtes pas obligé de le faire - utilisez Any pour conserver la valeur inchangée

Si vous n'avez pas besoin de valider une valeur, utilisez Any pour conserver la valeur inchangée.

from typing import Any

from pydantic import BaseModel


class Model(BaseModel):
    a: Any


model = Model(a=1)

Évitez les informations supplémentaires via des sous-classes de primitives

\=== "Ne fais pas ça"

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

\=== "Faites ceci"

```py
from pydantic import BaseModel


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

Utilisez l'union étiquetée, pas l'union

Une union taguée (ou union discriminée) est une union avec un champ qui indique de quel type il s'agit.

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'
    )

Voir Syndicats discriminés pour plus de détails.

Utiliser TypedDict sur des modèles imbriqués

Au lieu d'utiliser des modèles imbriqués, utilisez TypedDict pour définir la structure des données.

??? info "Comparaison des performances" Avec un simple benchmark, TypedDict est environ 2,5 fois plus rapide que les modèles imbriqués:

```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)
```

Évitez les validateurs wrap si vous vous souciez vraiment des performances

Les validateurs Wrap sont généralement plus lents que les autres validateurs. En effet, ils exigent que les données soient matérialisées en Python lors de la validation. Les validateurs Wrap peuvent être incroyablement utiles pour une logique de validation complexe, mais si vous recherchez les meilleures performances, vous devriez les éviter.

Échouer tôt avec FailFast

À partir de la version 2.8+, vous pouvez appliquer l'annotation FailFast aux types de séquence pour qu'ils échouent plus tôt si un élément de la séquence échoue à la validation. Si vous utilisez cette annotation, vous n'obtiendrez pas d'erreurs de validation pour le reste des éléments de la séquence en cas d'échec, vous échangez donc efficacement la visibilité contre les performances.

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]
    """

En savoir plus sur FailFast ici.


本文总阅读量