??? API „API-Dokumentation“ pydantic.main.BaseModel
Eine der wichtigsten Möglichkeiten, Schemata in Pydantic zu definieren, sind Modelle. Modelle sind einfach Klassen, die von pydantic.BaseModel
erben und Felder als annotierte Attribute definieren.
Sie können sich Modelle ähnlich wie Strukturen in Sprachen wie C oder als Anforderungen eines einzelnen Endpunkts in einer API vorstellen.
Modelle haben viele Ähnlichkeiten mit den Datenklassen von Python, wurden jedoch mit einigen subtilen, aber wichtigen Unterschieden entworfen, die bestimmte Arbeitsabläufe im Zusammenhang mit Validierung, Serialisierung und JSON-Schemagenerierung rationalisieren. Weitere Informationen hierzu finden Sie im Abschnitt „Datenklassen“ der Dokumentation.
Nicht vertrauenswürdige Daten können an ein Modell übergeben werden und nach der Analyse und Validierung garantiert Pydantic, dass die Felder der resultierenden Modellinstanz den im Modell definierten Feldtypen entsprechen.
!!! Hinweis „Validierung – eine absichtliche Fehlbezeichnung“ ### TL;DR
We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and
constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms,
even though in other contexts the term "validation" may be more restrictive.
---
### The long version
The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's
primary focus doesn't align precisely with the dictionary definition of "validation":
> ### validation
> _noun_
> the action of checking or proving the validity or accuracy of something.
In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified
types and constraints. Pydantic guarantees the types and constraints of the output, not the input data.
This distinction becomes apparent when considering that Pydantic's `ValidationError` is raised
when data cannot be successfully parsed into a model instance.
While this distinction may initially seem subtle, it holds practical significance.
In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data.
This can involve copying arguments passed to the constructor in order to perform coercion to a new type
without mutating the original input data. For a more in-depth understanding of the implications for your usage,
refer to the [Data Conversion](#data-conversion) and [Attribute Copies](#attribute-copies) sections below.
In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation")
precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term
for this process, we will consistently use it in our documentation.
While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate",
with "parse" reserved specifically for discussions related to [JSON parsing](../concepts/json.md).
Grundlegende Modellverwendung¶
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'Jane Doe'
In diesem Beispiel ist User
ein Modell mit zwei Feldern:
id
, eine Ganzzahl, die erforderlich istname
, eine Zeichenfolge, die nicht erforderlich ist (sie hat einen Standardwert).
user = Benutzer(id='123')
In diesem Beispiel ist user
eine Instanz von User
. Durch die Initialisierung des Objekts werden alle Parsing- und Validierungsvorgänge durchgeführt. Wenn kein ValidationError
ausgelöst wird, wissen Sie, dass die resultierende Modellinstanz gültig ist.
assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123
Weitere Details zur Zwangslogik von Pydantic finden Sie unter Datenkonvertierung . Auf Felder eines Modells kann als normale Attribute des user
zugegriffen werden. Die Zeichenfolge '123'
wurde entsprechend dem Feldtyp in ein int umgewandelt.
assert user.name == 'Jane Doe'
name
wurde bei der Initialisierung user
nicht festgelegt und hat daher den Standardwert.
assert user.model_fields_set == {'id'}
Die Felder, die bei der Initialisierung des Benutzers bereitgestellt wurden.
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
Entweder .model_dump()
oder dict(user)
stellt ein Diktat von Feldern bereit, aber .model_dump()
kann zahlreiche andere Argumente annehmen. (Beachten Sie, dass dict(user)
verschachtelte Modelle nicht rekursiv in Diktate umwandelt, .model_dump()
jedoch schon.)
user.id = 321
assert user.id == 321
Standardmäßig sind Modelle veränderbar und Feldwerte können durch Attributzuweisung geändert werden.
Modellmethoden und -eigenschaften¶
Das obige Beispiel zeigt nur die Spitze des Eisbergs dessen, was Modelle leisten können. Modelle verfügen über die folgenden Methoden und Attribute:
model_computed_fields
: ein Wörterbuch der berechneten Felder dieser Modellinstanz.model_construct()
: eine Klassenmethode zum Erstellen von Modellen ohne Durchführung einer Validierung. Siehe Modelle ohne Validierung erstellen .model_copy()
: gibt eine Kopie (standardmäßig eine flache Kopie) des Modells zurück. Siehe Serialisierung .model_dump()
: gibt ein Wörterbuch der Felder und Werte des Modells zurück. Siehe Serialisierung .model_dump_json()
: gibt eine JSON-String-Darstellung vonmodel_dump()
zurück. Siehe Serialisierung .model_extra
: Zusätzliche Felder werden während der Validierung festgelegt.model_fields_set
: Satz von Feldern, die bei der Initialisierung der Modellinstanz festgelegt wurden.model_json_schema()
: gibt ein jsonable-Wörterbuch zurück, das das Modell als JSON-Schema darstellt. Siehe JSON-Schema .model_parametrized_name()
: Berechnen Sie den Klassennamen für Parametrisierungen generischer Klassen.model_post_init()
: Führen Sie eine zusätzliche Initialisierung durch, nachdem das Modell initialisiert wurde.model_rebuild()
: Erstellen Sie das Modellschema neu, das auch die Erstellung rekursiver generischer Modelle unterstützt. Siehe Modellschema neu erstellen .model_validate()
: ein Dienstprogramm zum Laden eines beliebigen Objekts in ein Modell. Siehe Hilfsfunktionen .model_validate_json()
: ein Dienstprogramm zur Validierung der angegebenen JSON-Daten anhand des Pydantic-Modells. Siehe Hilfsfunktionen .
!!! Hinweis: Siehe BaseModel
für die Klassendefinition einschließlich einer vollständigen Liste von Methoden und Attributen.
!!! Tipp Weitere Informationen zu Änderungen gegenüber Pydantic V1 finden Sie unter Änderungen an pydantic.BaseModel
im Migrationsleitfaden .
Verschachtelte Modelle¶
Komplexere hierarchische Datenstrukturen können definiert werden, indem Modelle selbst als Typen in Annotationen verwendet werden.
from typing import List, Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple: str = 'x'
banana: str = 'y'
class Spam(BaseModel):
foo: Foo
bars: List[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
"""
foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
"""
print(m.model_dump())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}],
}
"""
Informationen zu selbstreferenzierenden Modellen finden Sie unter verschobene Anmerkungen .
!!! Hinweis Achten Sie beim Definieren Ihrer Modelle auf Namenskonflikte zwischen Ihrem Feldnamen und seinem Typ, einem zuvor definierten Modell oder einer importierten Bibliothek.
For example, the following would yield a validation error:
```py
from typing import Optional
from pydantic import BaseModel
class Boo(BaseModel):
int: Optional[int] = None
m = Boo(int=123) # errors
```
An error occurs since the field `int` is set to a default value of `None` and has the exact same name as its type, so both are interpreted to be `None`.
Modellschema neu erstellen¶
Das Modellschema kann mit model_rebuild()
neu erstellt werden. Dies ist nützlich für die Erstellung rekursiver generischer Modelle.
from pydantic import BaseModel, PydanticUserError
class Foo(BaseModel):
x: 'Bar'
try:
Foo.model_json_schema()
except PydanticUserError as e:
print(e)
"""
`Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.
For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
"""
class Bar(BaseModel):
pass
Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
'$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
'properties': {'x': {'$ref': '#/$defs/Bar'}},
'required': ['x'],
'title': 'Foo',
'type': 'object',
}
"""
Pydantic versucht automatisch zu ermitteln, wann dies erforderlich ist, und gibt einen Fehler aus, wenn dies nicht geschehen ist. Sie können jedoch model_rebuild()
proaktiv aufrufen, wenn Sie mit rekursiven Modellen oder Generika arbeiten.
In V2 ersetzte model_rebuild()
update_forward_refs()
aus V1. Es gibt einige geringfügige Unterschiede zum neuen Verhalten. Die größte Änderung besteht darin, dass beim Aufruf von model_rebuild()
für das äußerste Modell ein Kernschema erstellt wird, das zur Validierung des gesamten Modells (verschachtelte Modelle und alle), also aller Typen überhaupt, verwendet wird Ebenen müssen bereit sein, bevor model_rebuild()
aufgerufen wird.
Beliebige Klasseninstanzen¶
(Früher bekannt als „ORM-Modus“/ from_orm
.)
Pydantic-Modelle können auch aus beliebigen Klasseninstanzen erstellt werden, indem die Instanzattribute gelesen werden, die den Modellfeldnamen entsprechen. Eine häufige Anwendung dieser Funktionalität ist die Integration mit objektrelationalen Zuordnungen (ORMs).
Legen Sie dazu das Config-Attribut fest model_config['from_attributes'] = True
. Weitere Informationen finden Sie unter Model Config und ConfigDict.
Das Beispiel hier verwendet SQLAlchemy , aber der gleiche Ansatz sollte für jedes ORM funktionieren.
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated
from pydantic import BaseModel, ConfigDict, StringConstraints
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
public_key: Annotated[str, StringConstraints(max_length=20)]
name: Annotated[str, StringConstraints(max_length=63)]
domains: List[Annotated[str, StringConstraints(max_length=255)]]
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""
Reservierte Namen¶
Möglicherweise möchten Sie eine Column
nach einem reservierten SQLAlchemy-Feld benennen. In diesem Fall sind Field
praktisch:
import typing
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base
from pydantic import BaseModel, ConfigDict, Field
class MyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
metadata: typing.Dict[str, str] = Field(alias='metadata_')
Base = declarative_base()
class SQLModel(Base):
__tablename__ = 'my_table'
id = sa.Column('id', sa.Integer, primary_key=True)
# 'metadata' is reserved by SQLAlchemy, hence the '_'
metadata_ = sa.Column('metadata', sa.JSON)
sql_model = SQLModel(metadata_={'key': 'val'}, id=1)
pydantic_model = MyModel.model_validate(sql_model)
print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}
!!! Hinweis: Das obige Beispiel funktioniert, weil Aliase bei der Feldauffüllung Vorrang vor Feldnamen haben. Der Zugriff auf das metadata
von SQLModel
würde zu einem ValidationError
führen.
Verschachtelte Attribute¶
Bei der Verwendung von Attributen zum Analysieren von Modellen werden Modellinstanzen sowohl aus Attributen der obersten Ebene als auch aus tiefer verschachtelten Attributen erstellt.
Hier ist ein Beispiel, das das Prinzip demonstriert:
from typing import List
from pydantic import BaseModel, ConfigDict
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species
class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
self.name = name
self.age = age
self.pets = pets
class Pet(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
species: str
class Person(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
age: float = None
pets: List[Pet]
bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""
Fehlerbehandlung¶
Pydantic löst ValidationError
aus, wenn es einen Fehler in den zu validierenden Daten findet.
Unabhängig von der Anzahl der gefundenen Fehler wird eine einzelne Ausnahme vom Typ ValidationError
ausgelöst, und dieser ValidationError
enthält Informationen zu allen Fehlern und deren Entstehung.
Einzelheiten zu Standard- und benutzerdefinierten Fehlern finden Sie unter Fehlerbehandlung .
Als Demonstration:
from typing import List
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
list_of_ints: List[int]
a_float: float
data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
)
try:
Model(**data)
except ValidationError as e:
print(e)
"""
2 validation errors for Model
list_of_ints.2
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
a_float
Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
"""
Hilfsfunktionen¶
Pydantic bietet drei classmethod
Hilfsfunktionen für Modelle zum Parsen von Daten:
model_validate()
: Dies ist der__init__
Methode des Modells sehr ähnlich, außer dass sie ein Diktat oder ein Objekt anstelle von Schlüsselwortargumenten akzeptiert. Wenn das übergebene Objekt nicht validiert werden kann oder es sich nicht um ein Wörterbuch oder eine Instanz des betreffenden Modells handelt, wird einValidationError
ausgelöst.model_validate_json()
: Dies nimmt einen String oder Bytes und analysiert ihn als json und übergibt das Ergebnis dann anmodel_validate()
.-
model_validate_strings()
: Dies nimmt ein Diktat (kann verschachtelt werden) mit Zeichenfolgenschlüsseln und -werten und validiert die Daten im JSON -Modus, sodass diese Zeichenfolgen in die richtigen Typen umgewandelt werden können.from datetime import datetime from typing import Optional
from pydantic import BaseModel, ValidationError
class User(BaseModel): id: int name: str = 'John Doe' signup_ts: Optional[datetime] = None
m = User.model_validate({'id': 123, 'name': 'James'}) print(m)
> id=123 name='James' signup_ts=None¶
try: User.model_validate(['not', 'a', 'dict']) except ValidationError as e: print(e) """ 1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] """
m = User.model_validate_json('{"id": 123, "name": "James"}') print(m)
> id=123 name='James' signup_ts=None¶
try: m = User.model_validate_json('{"id": 123, "name": 123}') except ValidationError as e: print(e) """ 1 validation error for User name Input should be a valid string [type=string_type, input_value=123, input_type=int] """
try: m = User.model_validate_json('invalid JSON') except ValidationError as e: print(e) """ 1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] """
m = User.model_validate_strings({'id': '123', 'name': 'James'}) print(m)
> id=123 name='James' signup_ts=None¶
m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'} ) print(m)
> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)¶
try: m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True ) except ValidationError as e: print(e) """ 1 validation error for User signup_ts Input should be a valid datetime, invalid datetime separator, expected
T
,t
,_
or space [type=datetime_parsing, input_value='2024-04-01', input_type=str] """
Wenn Sie serialisierte Daten in einem anderen Format als JSON validieren möchten, sollten Sie die Daten selbst in ein Diktat laden und es dann an model_validate
übergeben.
!!! Hinweis Abhängig von den beteiligten Typen und Modellkonfigurationen können model_validate
und model_validate_json
ein unterschiedliches Validierungsverhalten aufweisen. Wenn Ihre Daten aus einer Nicht-JSON-Quelle stammen, Sie aber das gleiche Validierungsverhalten und die gleichen Fehler wünschen, die Sie von model_validate_json
erhalten würden, empfehlen wir Ihnen vorerst, beide Verwendungszwecke zu verwenden model_validate_json(json.dumps(data))
, oder verwenden Sie model_validate_strings
, wenn die Daten die Form eines (möglicherweise verschachtelten) Diktats mit Zeichenfolgenschlüsseln und -werten haben.
!!! Hinweis Weitere Informationen zum JSON-Parsing finden Sie im JSON -Abschnitt der Dokumentation.
!!! Hinweis Wenn Sie eine Instanz eines Modells an model_validate
übergeben, sollten Sie erwägen, revalidate_instances
in der Konfiguration des Modells festzulegen. Wenn Sie diesen Wert nicht festlegen, wird die Validierung für Modellinstanzen übersprungen. Sehen Sie sich das folgende Beispiel an:
\=== " revalidate_instances='never'
" ```py from pydantic import BaseModel
class Model(BaseModel):
a: int
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
```
\=== " revalidate_instances='always'
" ```py from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
a: int
model_config = ConfigDict(revalidate_instances='always')
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
try:
m2 = Model.model_validate(m)
except ValidationError as e:
print(e)
"""
1 validation error for Model
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
"""
```
Modelle ohne Validierung erstellen¶
Pydantic bietet außerdem die Methode model_construct()
, mit der Modelle ohne Validierung erstellt werden können. Dies kann zumindest in einigen Fällen nützlich sein:
- bei der Arbeit mit komplexen Daten, von denen bereits bekannt ist, dass sie gültig sind (aus Leistungsgründen)
- wenn eine oder mehrere der Validatorfunktionen nicht idempotent sind, oder
- wenn eine oder mehrere der Validierungsfunktionen Nebenwirkungen haben, die nicht ausgelöst werden sollen.
!!! Hinweis: In Pydantic V2 wurde die Leistungslücke zwischen BaseModel.__init__
und BaseModel.model_construct
erheblich verringert. Bei einfachen Modellen ist der Aufruf von BaseModel.__init__
möglicherweise sogar schneller. Wenn Sie model_construct()
aus Leistungsgründen verwenden, möchten Sie möglicherweise ein Profil Ihres Anwendungsfalls erstellen, bevor Sie davon ausgehen, dass model_construct()
schneller ist.
!!! Warnung model_construct()
führt keine Validierung durch, was bedeutet, dass ungültige Modelle erstellt werden können. Sie sollten die Methode model_construct()
immer nur mit Daten verwenden, die bereits validiert wurden oder denen Sie definitiv vertrauen.
from pydantic import BaseModel
class User(BaseModel):
id: int
age: int
name: str = 'John Doe'
original_user = User(id=123, age=32)
user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}
# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...
# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}
# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')
Das Schlüsselwortargument _fields_set
für model_construct()
ist optional, ermöglicht Ihnen jedoch eine genauere Angabe, welche Felder ursprünglich festgelegt wurden und welche nicht. Wenn es weggelassen wird, sind model_fields_set
nur die Schlüssel der bereitgestellten Daten.
Wenn beispielsweise im obigen Beispiel _fields_set
nicht angegeben wurde, wäre new_user.model_fields_set
{'id', 'age', 'name'}
.
Beachten Sie, dass für Unterklassen von RootModel
der Stammwert positionell an model_construct()
übergeben werden kann, anstatt ein Schlüsselwortargument zu verwenden.
Hier sind einige zusätzliche Hinweise zum Verhalten von model_construct()
:
- Wenn wir sagen „Es wird keine Validierung durchgeführt“, umfasst dies auch die Konvertierung von Diktaten in Modellinstanzen. Also, wenn Sie ein Feld haben Bei einem
Model
müssen Sie das innere Diktat selbst in ein Modell konvertieren, bevor Sie es anmodel_construct()
übergeben.- Insbesondere unterstützt die Methode
model_construct()
nicht die rekursive Konstruktion von Modellen aus Diktaten.
- Insbesondere unterstützt die Methode
- Wenn Sie keine Schlüsselwortargumente für Felder mit Standardwerten übergeben, werden weiterhin die Standardwerte verwendet.
- Bei Modellen mit privaten Attributen wird das Diktat
__pydantic_private__
genauso initialisiert wie beim Aufruf von__init__
. - Beim Erstellen einer Instanz mit
model_construct()
wird keine__init__
Methode aus dem Modell oder einer seiner übergeordneten Klassen aufgerufen, selbst wenn eine benutzerdefinierte__init__
Methode definiert ist.
!!! Hinweis „Über extra
Verhalten mit model_construct
“ * Für Modelle mit model_config['extra'] == 'allow'
, werden Daten, die nicht Feldern entsprechen, korrekt im Diktat __pydantic_extra__
und im __dict__
des Modells gespeichert. * Für Modelle mit model_config['extra'] == 'ignore'
, werden Daten, die nicht den Feldern entsprechen, ignoriert, d. h. nicht in __pydantic_extra__
oder __dict__
auf der Instanz gespeichert. * Im Gegensatz zu einem Aufruf von __init__
ist ein Aufruf von model_construct
mit model_config['extra'] == 'forbid'
löst keinen Fehler aus, wenn Daten vorhanden sind, die nicht den Feldern entsprechen. Vielmehr werden diese Eingabedaten einfach ignoriert.
Generische Modelle¶
Pydantic unterstützt die Erstellung generischer Modelle, um die Wiederverwendung einer gemeinsamen Modellstruktur zu erleichtern.
Um ein generisches Modell zu deklarieren, führen Sie die folgenden Schritte aus:
- Deklarieren Sie eine oder mehrere
typing.TypeVar
-Instanzen zur Parametrisierung Ihres Modells. - Deklarieren Sie ein pydantisches Modell, das von
pydantic.BaseModel
undtyping.Generic
erbt, wobei Sie dieTypeVar
Instanzen als Parameter antyping.Generic
übergeben. - Verwenden Sie die
TypeVar
Instanzen als Anmerkungen, wenn Sie sie durch andere Typen oder pydantische Modelle ersetzen möchten.
Hier ist ein Beispiel für die Verwendung einer generischen BaseModel
Unterklasse zum Erstellen eines einfach wiederverwendbaren HTTP-Antwort-Nutzlast-Wrappers:
from typing import Generic, List, Optional, TypeVar
from pydantic import BaseModel, ValidationError
DataT = TypeVar('DataT')
class DataModel(BaseModel):
numbers: List[int]
people: List[str]
class Response(BaseModel, Generic[DataT]):
data: Optional[DataT] = None
print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}
data = DataModel(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
1 validation error for Response[int]
data
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
"""
Wenn Sie model_config
festlegen oder @field_validator
oder andere Pydantic-Dekoratoren in Ihrer generischen Modelldefinition verwenden, werden diese auf die gleiche Weise auf parametrisierte Unterklassen angewendet wie beim Erben von einer BaseModel
Unterklasse. Alle in Ihrer generischen Klasse definierten Methoden werden ebenfalls vererbt.
Die Generika von Pydantic lassen sich auch problemlos in Typprüfer integrieren, sodass Sie alle Typprüfungen erhalten, die Sie erwarten würden, wenn Sie für jede Parametrisierung einen eigenen Typ deklarieren würden.
!!! Hinweis Intern erstellt Pydantic zur Laufzeit Unterklassen von BaseModel
, wenn generische Modelle parametrisiert werden. Diese Klassen werden zwischengespeichert, sodass durch die Verwendung generischer Modelle nur ein minimaler Mehraufwand entstehen sollte.
Um von einem generischen Modell zu erben und die Tatsache zu bewahren, dass es generisch ist, muss die Unterklasse auch von typing.Generic
erben:
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
class BaseClass(BaseModel, Generic[TypeX]):
X: TypeX
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
# Inherit from Generic[TypeX]
pass
# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1
Sie können auch eine generische Unterklasse eines BaseModel
erstellen, die die Typparameter in der Oberklasse teilweise oder vollständig ersetzt:
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')
class BaseClass(BaseModel, Generic[TypeX, TypeY]):
x: TypeX
y: TypeY
class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
z: TypeZ
# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3
Wenn der Name der konkreten Unterklassen wichtig ist, können Sie die Standardnamensgenerierung auch überschreiben:
from typing import Any, Generic, Tuple, Type, TypeVar
from pydantic import BaseModel
DataT = TypeVar('DataT')
class Response(BaseModel, Generic[DataT]):
data: DataT
@classmethod
def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
return f'{params[0].__name__.title()}Response'
print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')
Sie können parametrisierte generische Modelle als Typen in anderen Modellen verwenden:
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class ResponseModel(BaseModel, Generic[T]):
content: T
class Product(BaseModel):
name: str
price: float
class Order(BaseModel):
id: int
product: ResponseModel[Product]
product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""
!!! Tipp Wenn Sie ein parametrisiertes generisches Modell als Typ in einem anderen Modell verwenden (z product: ResponseModel[Product]
), stellen Sie sicher, dass Sie das generische Modell parametrisieren, wenn Sie die Modellinstanz initialisieren (wie response = ResponseModel[Product](content=product)
). Wenn Sie dies nicht tun, wird ein ValidationError
ausgelöst, da Pydantic den Typ des generischen Modells nicht anhand der an es übergebenen Daten ableitet.
Durch die Verwendung derselben TypeVar
in verschachtelten Modellen können Sie Typisierungsbeziehungen an verschiedenen Punkten in Ihrem Modell erzwingen:
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
T = TypeVar('T')
class InnerT(BaseModel, Generic[T]):
inner: T
class OuterT(BaseModel, Generic[T]):
outer: T
nested: InnerT[T]
nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
nested = InnerT[str](inner='a')
print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
print(e)
"""
2 validation errors for OuterT[int]
outer
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
nested
Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
"""
Wenn gebundene Typparameter verwendet werden und Typparameter nicht angegeben werden, behandelt Pydantic generische Modelle ähnlich wie integrierte generische Typen wie List
und Dict
:
- Wenn Sie vor der Instanziierung des generischen Modells keine Parameter angeben, werden diese als Grenze der
TypeVar
validiert. - Wenn die beteiligten
TypeVar
keine Grenzen haben, werden sie alsAny
behandelt.
Außerdem können, wie bei List
und Dict
, alle mit einer TypeVar
angegebenen Parameter später durch konkrete Typen ersetzt werden:
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
AT = TypeVar('AT')
BT = TypeVar('BT')
class Model(BaseModel, Generic[AT, BT]):
a: AT
b: BT
print(Model(a='a', b='a'))
#> a='a' b='a'
IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
typevar_model(a='a', b='a')
except ValidationError as exc:
print(exc)
"""
2 validation errors for Model[int, TypeVar]
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
b
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1
!!! Warnung Auch wenn dies möglicherweise keinen Fehler auslöst, raten wir dringend davon ab, parametrisierte Generika in Instanzprüfungen zu verwenden.
For example, you should not do `isinstance(my_model, MyGenericModel[int])`. However, it is fine to do `isinstance(my_model, MyGenericModel)`. (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)
If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like `class MyIntModel(MyGenericModel[int]): ...` and `isinstance(my_model, MyIntModel)`.
Wenn ein Pydantic-Modell in einer TypeVar
Grenze verwendet wird und der generische Typ nie parametrisiert wird, verwendet Pydantic die Grenze zur Validierung, behandelt den Wert jedoch im Hinblick auf die Serialisierung als Any
:
from typing import Generic, Optional, TypeVar
from pydantic import BaseModel
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized as Any
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'var2',
},
}
# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
message='We just had an error',
details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
Hier ist ein weiteres Beispiel für das obige Verhalten, das alle Permutationen bezüglich der gebundenen Spezifikation und der generischen Typparametrisierung auflistet:
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')
class IntValue(BaseModel):
value: int
class ItemBound(BaseModel, Generic[TBound]):
item: TBound
class ItemNoBound(BaseModel, Generic[TNoBound]):
item: TNoBound
item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))
# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}
Wenn Sie ein default=...
(verfügbar in Python >= 3.13 oder über typing-extensions
) oder Einschränkungen ( TypeVar('T', str, int)
; beachten Sie, dass Sie diese Form einer TypeVar
selten verwenden möchten), dann Der Standardwert oder die Standardeinschränkungen werden sowohl für die Validierung als auch für die Serialisierung verwendet, wenn die Typvariable nicht parametrisiert ist. Sie können dieses Verhalten mit pydantic.SerializeAsAny
überschreiben:
from typing import Generic, Optional
from typing_extensions import TypeVar
from pydantic import BaseModel, SerializeAsAny
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized using the default's serializer
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[SerializeAsAny[ErrorDataT]]
# serialized as Any
error = SerializeAsAnyError(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'baz',
},
}
!!! note Hinweis: Wenn Sie ein Generikum nicht parametrisieren, kann es zu Problemen kommen, da die Validierung anhand der Grenze des Generikums zu Datenverlust führen könnte. Siehe das Beispiel unten:
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TItem = TypeVar('TItem', bound='ItemBase')
class ItemBase(BaseModel): ...
class IntItem(ItemBase):
value: int
class ItemHolder(BaseModel, Generic[TItem]):
item: TItem
loaded_data = {'item': {'value': 1}}
print(ItemHolder(**loaded_data).model_dump()) # (1)!
#> {'item': {}}
print(ItemHolder[IntItem](**loaded_data).model_dump()) # (2)!
#> {'item': {'value': 1}}
- Wenn das generische Element nicht parametrisiert ist, werden die Eingabedaten anhand der generischen Grenze validiert. Da
ItemBase
keine Felder hat, gehen dieitem
verloren. - In diesem Fall werden die Laufzeittypinformationen explizit über die generische Parametrisierung bereitgestellt, sodass die Eingabedaten anhand der
IntItem
-Klasse validiert werden und die Serialisierungsausgabe den Erwartungen entspricht.
Dynamische Modellerstellung¶
??? API „API-Dokumentation“ pydantic.main.create_model
In einigen Fällen ist es wünschenswert, ein Modell zu erstellen, bei dem Laufzeitinformationen zum Spezifizieren der Felder verwendet werden. Zu diesem Zweck stellt Pydantic die Funktion create_model
zur Verfügung, mit der Modelle im Handumdrehen erstellt werden können:
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model(
'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
Hier sind StaticFoobarModel
und DynamicFoobarModel
identisch.
Felder werden durch eine der folgenden Tupelformen definiert:
(<type>, <default value>)
(<type>, Field(...))
typing.Annotated[<type>, Field(...)]
Die Verwendung eines Field(...)
-Aufrufs als zweites Argument im Tupel (der Standardwert) ermöglicht eine erweiterte Feldkonfiguration. Somit gilt Folgendes:
from pydantic import BaseModel, Field, create_model
DynamicModel = create_model(
'DynamicModel',
foo=(str, Field(..., description='foo description', alias='FOO')),
)
class StaticModel(BaseModel):
foo: str = Field(..., description='foo description', alias='FOO')
Die speziellen Schlüsselwortargumente __config__
und __base__
können zum Anpassen des neuen Modells verwendet werden. Dazu gehört die Erweiterung eines Basismodells um zusätzliche Felder.
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 123
BarModel = create_model(
'BarModel',
apple=(str, 'russet'),
banana=(str, 'yellow'),
__base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])
Sie können Validatoren auch hinzufügen, indem Sie ein Diktat an das Argument __validators__
übergeben.
from pydantic import ValidationError, create_model, field_validator
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {
'username_validator': field_validator('username')(username_alphanumeric)
}
UserModel = create_model(
'UserModel', username=(str, ...), __validators__=validators
)
user = UserModel(username='scolvin')
print(user)
#> username='scolvin'
try:
UserModel(username='scolvi%n')
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
username
Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
"""
!!! Hinweis So wählen Sie ein dynamisch erstelltes Modell aus:
- the model must be defined globally
- it must provide `__module__`
RootModel
und benutzerdefinierte Root-Typen¶
??? API „API-Dokumentation“ pydantic.root_model.RootModel
Pydantic-Modelle können mit einem „benutzerdefinierten Stammtyp“ definiert werden, indem eine Unterklasse von pydantic.RootModel
gebildet wird.
Der Stammtyp kann jeder von Pydantic unterstützte Typ sein und wird durch den generischen Parameter für RootModel
angegeben. Der Stammwert kann über das erste und einzige Argument an das Modell __init__
oder model_validate
übergeben werden.
Hier ist ein Beispiel, wie das funktioniert:
from typing import Dict, List
from pydantic import RootModel
Pets = RootModel[List[str]]
PetsByName = RootModel[Dict[str, str]]
print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets.model_json_schema())
"""
{'items': {'type': 'string'}, 'title': 'RootModel[List[str]]', 'type': 'array'}
"""
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
Wenn Sie direkt auf Elemente im root
zugreifen oder über die Elemente iterieren möchten, können Sie benutzerdefinierte Funktionen __iter__
und __getitem__
implementieren, wie im folgenden Beispiel gezeigt.
from typing import List
from pydantic import RootModel
class Pets(RootModel):
root: List[str]
def __iter__(self):
return iter(self.root)
def __getitem__(self, item):
return self.root[item]
pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
Sie können Unterklassen des parametrisierten Wurzelmodells auch direkt erstellen:
from typing import List
from pydantic import RootModel
class Pets(RootModel[List[str]]):
def describe(self) -> str:
return f'Pets: {", ".join(self.root)}'
my_pets = Pets.model_validate(['dog', 'cat'])
print(my_pets.describe())
#> Pets: dog, cat
Falsche Unveränderlichkeit¶
Modelle können über model_config['frozen'] = True
so konfiguriert werden, dass sie unveränderlich sind. Wenn dies festgelegt ist, führt der Versuch, die Werte von Instanzattributen zu ändern, zu Fehlern. Weitere Einzelheiten finden Sie in der API-Referenz.
!!! Hinweis Dieses Verhalten wurde in Pydantic V1 über die Konfigurationseinstellung allow_mutation = False
erreicht. Dieses Konfigurationsflag ist in Pydantic V2 veraltet und wurde durch frozen
ersetzt.
!!! Warnung In Python wird Unveränderlichkeit nicht erzwungen. Entwickler haben die Möglichkeit, Objekte zu ändern, die herkömmlicherweise als „unveränderlich“ gelten, wenn sie dies wünschen.
from pydantic import BaseModel, ConfigDict, ValidationError
class FooBarModel(BaseModel):
model_config = ConfigDict(frozen=True)
a: str
b: dict
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except ValidationError as e:
print(e)
"""
1 validation error for FooBarModel
a
Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
"""
print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}
Der Versuch, a
zu ändern, verursachte einen Fehler und a
bleibt unverändert. Das Diktat b
ist jedoch veränderbar, und die Unveränderlichkeit von foobar
verhindert nicht, dass b
geändert wird.
Abstrakte Basisklassen¶
Pydantic-Modelle können zusammen mit den Abstract Base Classes (ABCs) von Python verwendet werden.
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel, abc.ABC):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
Feldbestellung¶
Die Feldreihenfolge wirkt sich auf folgende Weise auf Modelle aus:
- Die Feldreihenfolge bleibt im Modellschema erhalten
- Die Feldreihenfolge bleibt bei Validierungsfehlern erhalten
-
Die Feldreihenfolge wird durch
.model_dump()
und.model_dump_json()
usw. beibehalten.from pydantic import BaseModel, ValidationError
class Model(BaseModel): a: int b: int = 2 c: int = 1 d: int = 0 e: float
print(Model.model_fields.keys())
> dict_keys(['a', 'b', 'c', 'd', 'e'])¶
m = Model(e=2, a=1) print(m.model_dump())
>¶
try: Model(a='x', b='x', c='x', d='x', e='x') except ValidationError as err: error_locations = [e['loc'] for e in err.errors()]
print(error_locations)
> [('a',), ('b',), ('c',), ('d',), ('e',)]¶
Erforderliche Felder¶
Um ein Feld als erforderlich zu deklarieren, können Sie es mithilfe einer Annotation oder einer Annotation in Kombination mit einer Field
deklarieren. Sie können Ellipsis
/ ...
auch verwenden, um hervorzuheben, dass ein Feld erforderlich ist, insbesondere wenn Sie den Field
Konstruktor verwenden.
Die Field
wird hauptsächlich zum Konfigurieren von Einstellungen wie alias
oder description
für ein Attribut verwendet. Der Konstruktor unterstützt Ellipsis
/ ...
als einziges Positionsargument. Dies wird verwendet, um anzugeben, dass das Feld obligatorisch ist, obwohl es der Typhinweis ist, der diese Anforderung erzwingt.
from pydantic import BaseModel, Field
class Model(BaseModel):
a: int
b: int = ...
c: int = Field(..., alias='C')
Hier sind a
, b
und c
alle erforderlich. Diese Verwendung von b: int = ...
funktioniert jedoch mit mypy nicht ordnungsgemäß und sollte ab Version 1.0 in den meisten Fällen vermieden werden.
!!! Hinweis In Pydantic V1 erhielten Felder, die mit Optional
oder Any
versehen waren, implizit den Standardwert None
, selbst wenn kein Standardwert explizit angegeben wurde. Dieses Verhalten hat sich in Pydantic V2 geändert und es gibt keine Typanmerkungen mehr, die dazu führen, dass ein Feld einen impliziten Standardwert hat.
See [the migration guide](../migration.md#required-optional-and-nullable-fields) for more details on changes
to required and nullable fields.
Felder mit nicht hashbaren Standardwerten¶
Eine häufige Fehlerquelle in Python ist die Verwendung eines veränderlichen Objekts als Standardwert für ein Funktions- oder Methodenargument, da bei jedem Aufruf dieselbe Instanz wiederverwendet wird.
Das Modul dataclasses
löst in diesem Fall tatsächlich einen Fehler aus, der darauf hinweist, dass Sie das Argument default_factory
für dataclasses.field
verwenden sollten.
Pydantic unterstützt auch die Verwendung einer default_factory
für nicht hashbare Standardwerte, dies ist jedoch nicht erforderlich. Für den Fall, dass der Standardwert nicht hashbar ist, kopiert Pydantic den Standardwert beim Erstellen jeder Instanz des Modells tief:
from typing import Dict, List
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)
#> [{}]
Felder mit dynamischen Standardwerten¶
Wenn Sie ein Feld mit einem Standardwert deklarieren, möchten Sie möglicherweise, dass dieser dynamisch ist (dh für jedes Modell unterschiedlich). Zu diesem Zweck möchten Sie möglicherweise eine default_factory
verwenden.
Hier ist ein Beispiel:
from datetime import datetime, timezone
from uuid import UUID, uuid4
from pydantic import BaseModel, Field
def datetime_now() -> datetime:
return datetime.now(timezone.utc)
class Model(BaseModel):
uid: UUID = Field(default_factory=uuid4)
updated: datetime = Field(default_factory=datetime_now)
m1 = Model()
m2 = Model()
assert m1.uid != m2.uid
Weitere Informationen finden Sie in der Dokumentation der Field
.
Automatisch ausgeschlossene Attribute¶
Klassenvars¶
Mit typing.ClassVar
annotierte Attribute werden von Pydantic ordnungsgemäß als Klassenvariablen behandelt und werden nicht zu Feldern in Modellinstanzen:
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
x: int = 2
y: ClassVar[int] = 1
m = Model()
print(m)
#> x=2
print(Model.y)
#> 1
Private Modellattribute¶
??? API „API-Dokumentation“ pydantic.fields.PrivateAttr
Attribute, deren Name einen führenden Unterstrich hat, werden von Pydantic nicht als Felder behandelt und sind nicht im Modellschema enthalten. Stattdessen werden diese in ein „privates Attribut“ umgewandelt, das bei Aufrufen von __init__
, model_validate
usw. nicht validiert oder gar gesetzt wird.
!!! Hinweis Ab Pydantic v2.1.0 erhalten Sie einen NameError, wenn Sie versuchen, die Field
Funktion mit einem privaten Attribut zu verwenden. Da private Attribute nicht als Felder behandelt werden, kann die Funktion Field() nicht angewendet werden.
Hier ist ein Anwendungsbeispiel:
from datetime import datetime
from random import randint
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str
def __init__(self, **data):
super().__init__(**data)
# this could also be done with default_factory
self._secret_value = randint(1, 5)
m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3
Private Attributnamen müssen mit einem Unterstrich beginnen, um Konflikte mit Modellfeldern zu vermeiden. Dunder-Namen (z. B. __attr__
) werden jedoch nicht unterstützt.
Datenkonvertierung¶
Pydantic wandelt Eingabedaten möglicherweise um, um eine Anpassung an Modellfeldtypen zu erzwingen. In einigen Fällen kann dies zu einem Informationsverlust führen. Zum Beispiel:
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: float
c: str
print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}
Dies ist eine bewusste Entscheidung von Pydantic und häufig der nützlichste Ansatz. Eine ausführlichere Diskussion zu diesem Thema finden Sie hier .
Dennoch wird auch eine strenge Typprüfung unterstützt.
Modellsignatur¶
Die Signatur aller Pydantic-Modelle wird basierend auf ihren Feldern generiert:
import inspect
from pydantic import BaseModel, Field
class FooModel(BaseModel):
id: int
name: str = None
description: str = 'Foo'
apple: int = Field(alias='pear')
print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None
Eine genaue Signatur ist für Selbstbeobachtungszwecke und Bibliotheken wie FastAPI
oder hypothesis
nützlich.
Die generierte Signatur berücksichtigt auch benutzerdefinierte __init__
Funktionen:
import inspect
from pydantic import BaseModel
class MyModel(BaseModel):
id: int
info: str = 'Foo'
def __init__(self, id: int = 1, *, bar: str, **data) -> None:
"""My custom init!"""
super().__init__(id=id, bar=bar, **data)
print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None
Um in die Signatur aufgenommen zu werden, muss der Alias oder Name eines Feldes ein gültiger Python-Bezeichner sein. Pydantic priorisiert beim Generieren der Signatur den Alias eines Felds vor seinem Namen, kann jedoch den Feldnamen verwenden, wenn der Alias kein gültiger Python-Bezeichner ist.
Wenn sowohl der Alias als auch der Name eines Felds keine gültigen Bezeichner sind (was durch die exotische Verwendung von create_model
möglich sein kann), wird ein **data
-Argument hinzugefügt. Darüber hinaus ist das Argument **data
immer in der Signatur vorhanden, wenn model_config['extra'] == 'allow'
.
Strukturmustervergleich¶
Pydantic unterstützt den strukturellen Mustervergleich für Modelle, wie durch PEP 636 in Python 3.10 eingeführt.
from pydantic import BaseModel
class Pet(BaseModel):
name: str
species: str
a = Pet(name='Bones', species='dog')
match a:
# match `species` to 'dog', declare and initialize `dog_name`
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
# default case
case _:
print('No dog matched')
!!! Hinweis Eine Match-Case-Anweisung mag so aussehen, als ob sie ein neues Modell erstellt, aber lassen Sie sich nicht täuschen; Es handelt sich lediglich um syntaktischen Zucker, um ein Attribut abzurufen und es entweder zu vergleichen oder zu deklarieren und zu initialisieren.
Attributkopien¶
In vielen Fällen werden an den Konstruktor übergebene Argumente kopiert, um eine Validierung und gegebenenfalls einen Zwang durchzuführen.
Beachten Sie in diesem Beispiel, dass sich die ID der Liste ändert, nachdem die Klasse erstellt wurde, da sie während der Validierung kopiert wurde:
from typing import List
from pydantic import BaseModel
class C1:
arr = []
def __init__(self, in_arr):
self.arr = in_arr
class C2(BaseModel):
arr: List[int]
arr_orig = [1, 9, 10, 3]
c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False
!!! Hinweis: Es gibt Situationen, in denen Pydantic keine Attribute kopiert, beispielsweise bei der Übergabe von Modellen – wir verwenden das Modell so, wie es ist. Sie können dieses Verhalten durch eine Einstellung außer Kraft setzen model_config['revalidate_instances'] = 'always'
.
Zusätzliche Felder¶
Standardmäßig geben Pydantic-Modelle keine Fehler aus, wenn Sie Daten für nicht erkannte Felder bereitstellen. Sie werden einfach ignoriert:
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
Wenn Sie möchten, dass dies einen Fehler auslöst, können Sie dies über model_config
erreichen:
from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='forbid')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
"""
Um stattdessen alle zusätzlichen bereitgestellten Daten beizubehalten, können Sie extra='allow'
festlegen. Die zusätzlichen Felder werden dann in BaseModel.__pydantic_extra__
gespeichert:
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}
Standardmäßig wird auf diese zusätzlichen Elemente keine Validierung angewendet, Sie können jedoch einen Typ für die Werte festlegen, indem Sie die Typanmerkung für __pydantic_extra__
überschreiben:
from typing import Dict
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class Model(BaseModel):
__pydantic_extra__: Dict[str, int] = Field(init=False) # (1)!
x: int
model_config = ConfigDict(extra='allow')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
- Das
= Field(init=False)
hat zur Laufzeit keine Auswirkung, verhindert jedoch, dass das Feld__pydantic_extra__
von Typprüfern als Argument für die Methode__init__
des Modells behandelt wird.
Die gleichen Konfigurationen gelten für TypedDict
und dataclass
', mit der Ausnahme, dass die Konfiguration durch Festlegen des Attributs __pydantic_config__
der Klasse auf ein gültiges ConfigDict
gesteuert wird.
本文总阅读量次