目錄
- 基本原理
- 安裝
- 用法
- dataclasses
- 嵌套的dataclasses
- 選擇
- 驗證器
- pre和whole驗證
- always驗證
- dataclass驗證器
- 欄位檢查
- 遞回模型
- 模式創建
- 錯誤處理
- datetime型別
- Exotic型別
- Secret型別
- JSON型別
- 自定義資料型別
- 幫助函式
- 模型的 Config 類
- 設定
- 動態模型創建
- 與mypy一起使用
- 嚴格的可選項
- 必須欄位和mypy
- 偽不可變性
- 復制
- 序列化
- JSON序列化
- Pickle序列化
- 抽象基類
- 延遲注解
Pydantic 是一個使用Python型別提示來進行資料驗證和設定管理的庫,Pydantic定義資料應該如何使用純Python規范用并進行驗證,PEP 484 從Python3.5開始引入了型別提示的功能,PEP 526 使用Python3.6中的變數注釋語法對其進行了拓展,Pydantic使用這些注釋來驗證不受信任的資料是否采用了您想要的形式,
示例:
from datetime import datetime
from typing import List
from pydantic import BaseModel
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: datetime = None
friends: List[int] = []
external_data = https://www.cnblogs.com/fengqiang626/p/{'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
這里發生了什么:
- id 是 int 型別;注釋宣告告訴pydantic該欄位是必須的,如果可能,字串、位元組或浮點數將強制轉換為int,否則將引發例外,
- name 從默認值推斷為其為 str 型別,該欄位不是必須的,因為它有默認值,
- signup_ts 是 datetime 型別,該欄位不是必須的,默認值為 None,pydantic會將表示unix時間戳(例如1496498400)的 int 型別或表示時間和日期的字串處理成 datetime 型別,
- friends 使用Python的 typing 系統,該欄位是必須的,并且必須是元素為整數的串列,默認值為一個空串列,
如果驗證失敗,pydantic會拋出一個錯誤,列出錯誤的原因:
from pydantic import ValidationError
try:
User(signup_ts='broken', friends=[1, 2, 'not number'])
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"id"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"signup_ts"
],
"msg": "invalid datetime format",
"type": "type_error.datetime"
},
{
"loc": [
"friends",
2
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
"""
基本原理
pydantic使用了一些很酷的新語言特性,但我為什么要使用它呢?
- 不需要太高的學習成本
不需要學習新的模式定義微語言,如果您了解Python(也許略讀了 型別提示檔案),您就知道如何使用pydantic,
- 與你的IDE/linter/brain配合得很好
因為pydantic資料結構只是您定義的類的實體;自動完成、linting、mypy和您的直覺都應該能夠正確地處理經過驗證的資料,
- 多用途
pydantic的 BaseSettions 類允許在 驗證此請求資料 背景關系和 加載我的系統設定 背景關系中使用它,主要區別在于,系統設定可以由環境變數更改默認值,而且通常需要更復雜的物件,如DSNs和Python物件,
- 快速
pydantic比其他所有測驗庫都要快,
- 可以驗證復雜結構
使用遞回pydantic模型、typing 模塊的 List 和 Dict 等,并且驗證器可以清晰、輕松地定義復雜的資料模式,然后進行檢查,
- 可拓展
pydantic允許定義自定義資料型別,或者您可以使用使用validator裝飾器裝飾的模型上的方法來擴展驗證器,
安裝
pip install pydantic
Pydantic除了Python3.6或Python3.7(和Python3.6中的 dataclasses 包)之外,不需要其他依賴項,
如果想讓pydantic更加快速的決議JSON,你可以添加 ujson 作為可選的依賴項,類似的,pydantic 的email驗證依賴于 email-validator :
pip install pydantic[ujson]
# or
pip install pydantic[email]
# or just
pip install pydantic[ujson,email]
當然,你也可以使用 pip install … 手動安裝這些依賴項,
用法
pydantic使用 typing 型別定義更復雜的物件:
from typing import Dict, List, Optional, Sequence, Set, Tuple, Union
from pydantic import BaseModel
class Model(BaseModel):
simple_list: list = None
list_of_ints: List[int] = None
simple_tuple: tuple = None
tuple_of_different_types: Tuple[int, float, str, bool] = None
simple_dict: dict = None
dict_str_float: Dict[str, float] = None
simple_set: set = None
set_bytes: Set[bytes] = None
str_or_bytes: Union[str, bytes] = None
none_or_str: Optional[str] = None
sequence_of_ints: Sequence[int] = None
compound: Dict[Union[str, bytes], List[Set[int]]] = None
print(Model(simple_list=['1', '2', '3']).simple_list) # > ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints) # > [1, 2, 3]
print(Model(simple_tuple=(1, 2, 3, 4)).simple_tuple) # > (1, 2, 3, 4)
print(Model(tuple_of_different_types=[1, 2, 3, 4]).tuple_of_different_types) # > (1, 2.0, '3', True)
print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict) # > {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float) # > {'a': 1.0, 'b': 2.0}
print(Model(simple_set={1, 2, 3, 4}).simple_set)
print(Model(set_bytes={b'1', b'2', b'3', b'4'}).set_bytes)
print(Model(str_or_bytes='hello world').str_or_bytes)
print(Model(str_or_bytes=b'hello world').str_or_bytes)
print(Model(none_or_str='hello world').none_or_str)
print(Model(none_or_str=None).none_or_str)
print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints) # > [1, 2, 3, 4]
print(Model(compound={'name': [{1, 2, 3}], b'entitlement': [{10, 5}]}).compound)
dataclasses
# 注意:v0.14 版本的新功能,
如果不希望使用pydantic的 BaseModel,則可以在標準 dataclasses 上獲得相同的資料驗證(在Python 3.7中引入),
dataclasses 在Python3.6中使用 dataclasses backport package 來作業,
from datetime import datetime
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
signup_ts: datetime = None
user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
# > User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
您可以使用所有標準的pydantic欄位型別,得到的資料類將與使用標準庫的 dataclass 裝飾器創建的資料類相同,
pydantic.dataclasses.dataclass 的引數與標準裝飾器相同,除了一個與 Config 具有相同含義的額外關鍵字引數 config,
注意:作為pydantic的 dataclasses 能夠很好地處理 mypy的副作用, 配置引數將在IDE和mypy中顯示為不可用,使用 @dataclass(..., config=Config) # type: ignore 作為解決方案,查看 python/mypy#6239 來了解為什么要這樣,
嵌套的dataclasses
從 v0.17 版本開始,dataclasses 和正常的模型都支持嵌套的 dataclasses,
from pydantic import UrlStr
from pydantic.dataclasses import dataclass
@dataclass
class NavbarButton:
href: UrlStr
@dataclass
class Navbar:
button: NavbarButton
navbar = Navbar(button=('https://example.com',))
print(navbar)
# > Navbar(button=NavbarButton(href='https://example.com'))
dataclasses 屬性可以由元組、字典或該 dataclass 的示例來填充,
選擇
Pydantic使用Python的標準 enum 類定義選擇:
from enum import Enum, IntEnum
from pydantic import BaseModel
class FruitEnum(str, Enum):
pear = 'pear'
banana = 'banana'
class ToolEnum(IntEnum):
spanner = 1
wrench = 2
class CookingModel(BaseModel):
fruit: FruitEnum = FruitEnum.pear
tool: ToolEnum = ToolEnum.spanner
print(CookingModel())
# > CookingModel fruit=<FruitEnum.pear: 'pear'> tool=<ToolEnum.spanner: 1>
print(CookingModel(tool=2, fruit='banana'))
# > CookingModel fruit=<FruitEnum.banana: 'banana'> tool=<ToolEnum.wrench: 2>
print(CookingModel(fruit='other'))
# will raise a validation error
驗證器
自定義驗證和物件之間的復雜關系可以使用 validator 裝飾器來獲得,
validator 裝飾器通過裝飾類中的方法來驗證欄位,被裝飾的方法必須是 類方法,
validator 裝飾器有幾個可選的引數:
- fields:要呼叫裝飾器進行驗證的欄位,一個驗證器可以應用于多個欄位,可以通過顯式指定欄位名稱的方式逐一指定欄位,也可以通過 * 以解包的方式指定所有的欄位,這意味著將為所有欄位呼叫驗證器,
- pre:是否應該在標準驗證器之前呼叫此驗證器(否則在之后),如果為 True,則在呼叫標準驗證器之前呼叫,否則在之后呼叫,
- whole:對于復雜的物件,例如 set 或者 list,是驗證物件中的每個元素或者驗證整個物件,如果為 True,則驗證物件本身,如果為 False,則驗證物件中的每個元素,
- always:是否在值缺失的情況下仍然調這個方法和其他驗證器,
- check_fields:是否檢查模型上是否存在欄位,
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
password1: str
password2: str
@validator('name')
def name_must_contain_space(cls, v):
if ' ' not in v:
raise ValueError('must contain a space')
return v.title()
@validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
UserModel(name='samuel colvin', password1='zxcvbn', password2='zxcvbn')
# <UserModel name='Samuel Colvin' password1='zxcvbn' password2='zxcvbn'>
try:
UserModel(name='samuel', password1='zxcvbn', password2='zxcvbn2')
except ValidationError as e:
print(e)
# 2 validation errors
# name
# must contain a space (type=value_error)
# password2
# passwords do not match (type=value_error)
需要注意的幾件事情:
- validator 裝飾的是類方法,它接收的第一個值是 UserModel 而不是 UserModel 的實體,
- 它們的簽名可以是 (cls, value) 或 (cls, value, values, config, field),從 v0.20開始,任何 (values, config, field) 的子集都是允許的,例如 (cls, value, field),但是,由于檢查 validator 的方式,可變的關鍵字引數(**kwargs)必須叫做 kwargs,
- validator 應該回傳新的值或者引發 ValueError 或 TypeError 例外,
- 當驗證器依賴于其他值時,應該知道:
- 驗證是按照欄位的定義順序來完成的,例如,這里 password2 可以訪問 password1 (和 name),但是password1 不能訪問 password2,應該注意以下關于欄位順序和必填欄位的警告,
- 如果在其另一個欄位上驗證失敗(或者那個欄位缺失),則其不會被包含在 values 中,因此,上面的例子中包含 if 'password1' in values and … 陳述句,
注意:從v0.18開始,磨人不會對字典的鍵呼叫驗證器,如果希望驗證鍵,請使用 whole ,
pre和whole驗證
import json
from typing import List
from pydantic import BaseModel, ValidationError, validator
class DemoModel(BaseModel):
numbers: List[int] = []
people: List[str] = []
@validator('people', 'numbers', pre=True, whole=True)
def json_decode(cls, v):
if isinstance(v, str):
try:
return json.loads(v)
except ValueError:
pass
return v
@validator('numbers')
def check_numbers_low(cls, v):
if v > 4:
raise ValueError(f'number too large {v} > 4')
return v
@validator('numbers', whole=True)
def check_sum_numbers_low(cls, v):
if sum(v) > 8:
raise ValueError(f'sum of numbers greater than 8')
return v
DemoModel(numbers='[1, 2, 1, 3]')
# <DemoModel numbers=[1, 2, 1, 3] people=[]>
try:
DemoModel(numbers='[1, 2, 5]')
except ValidationError as e:
print(e)
# 1 validation error
# numbers -> 2
# number too large 5 > 4 (type=value_error)
try:
DemoModel(numbers=[3, 3, 3])
except ValidationError as e:
print(e)
# 1 validation error
# numbers
# sum of numbers greater than 8 (type=value_error)
always驗證
出于性能原因,默認情況下,對于不提供值的欄位不呼叫驗證器,然而,在某些情況下,總是呼叫驗證器是有用的或必需的,例如設定動態默認值,
from datetime import datetime
from pydantic import BaseModel, validator
class DemoModel(BaseModel):
ts: datetime = None
@validator('ts', pre=True, always=True)
def set_ts_now(cls, v):
return v or datetime.now()
DemoModel()
# <DemoModel ts=datetime.datetime(2019, 4, 26, 8, 26, 13, 74477)>
DemoModel(ts='2017-11-08T14:00')
# <DemoModel ts=datetime.datetime(2017, 11, 8, 14, 0)>
dataclass驗證器
驗證器在 dataclasses 上也可以作業:
from datetime import datetime
from pydantic import validator
from pydantic.dataclasses import dataclass
@dataclass
class DemoDataclass:
ts: datetime = None
@validator('ts', pre=True, always=True)
def set_ts_now(cls, v):
return v or datetime.now()
DemoDataclass()
DemoDataclass(ts=None)
DemoDataclass(ts='2017-11-08T14:00')
DemoDataclass(ts=datetime.datetime(2017, 11, 8, 14, 0))
欄位檢查
在類創建時,將檢查驗證器,以確認它們指定的欄位實際上存在于模型中,
但是,有時并不需要這樣做:當定義一個驗證器來驗證繼承模型上的欄位時,在這種情況下,您應該在驗證器上設定 check_fields=False,
遞回模型
更復雜的層次資料結構可以使用模型作為注解中的型別來定義,
... 只表示與上面的注解宣告相同的 Required,
from typing import List
from pydantic import BaseModel
class Foo(BaseModel):
count: int = ...
size: float = None
class Bar(BaseModel):
apple = 'x'
banana = 'y'
class Spam(BaseModel):
foo: Foo = ...
bars: List[Bar] = ...
s = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
# <Spam foo=<Foo count=4 size=None> bars=[<Bar apple='x1' banana='y'>, <Bar apple='x2' banana='y'>]>
s.dict()
# {'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}
模式創建
Pydantic會自動從模型創建JSON Schema,
from enum import Enum
from pydantic import BaseModel, Schema
class FooBar(BaseModel):
count: int
size: float = None
class Gender(str, Enum):
male = 'male'
female = 'female'
other = 'other'
not_given = 'not_given'
class MainModel(BaseModel):
"""
This is the description of the main model
"""
foo_bar: FooBar = Schema(...)
gender: Gender = Schema(
None,
alias='Gender',
)
snap: int = Schema(
42,
title='The Snap',
description='this is the value of snap',
gt=30,
lt=50,
)
class Config:
title = 'Main'
print(MainModel.schema())
# {'title': 'Main', 'description': 'This is the description of the main model', 'type': 'object', 'properties': {'foo_bar': {'$ref': '#/definitions/FooBar'}, 'Gender': {'title': 'Gender', 'enum': ['male', 'female', 'other', 'not_given'], 'type': 'string'}, 'snap': {'title': 'The Snap', 'description': 'this is the value of snap', 'default': 42, 'exclusiveMinimum': 30, 'exclusiveMaximum': 50, 'type': 'integer'}}, 'required': ['foo_bar'], 'definitions': {'FooBar': {'title': 'FooBar', 'type': 'object', 'properties': {'count': {'title': 'Count', 'type': 'integer'}, 'size': {'title': 'Size', 'type': 'number'}}, 'required': ['count']}}}
print(MainModel.schema_json(indent=4))
輸出:
{
"title": "Main",
"description": "This is the description of the main model",
"type": "object",
"properties": {
"foo_bar": {
"$ref": "#/definitions/FooBar"
},
"Gender": {
"title": "Gender",
"enum": [
"male",
"female",
"other",
"not_given"
],
"type": "string"
},
"snap": {
"title": "The Snap",
"description": "this is the value of snap",
"default": 42,
"exclusiveMinimum": 30,
"exclusiveMaximum": 50,
"type": "integer"
}
},
"required": [
"foo_bar"
],
"definitions": {
"FooBar": {
"title": "FooBar",
"type": "object",
"properties": {
"count": {
"title": "Count",
"type": "integer"
},
"size": {
"title": "Size",
"type": "number"
}
},
"required": [
"count"
]
}
}
}
生成的模式符合以下規范: JSON Schema Core,JSON Schema Validation and OpenAPI,
BaseModel.schema 會回傳一個表示JSON Schema的字典物件,
BaseModel.schema_json 會回傳一個表示表示JSON Schema的字串,
使用的子模型被添加到JSON Schema中,并根據規范進行參考,所有子模型(及其子模型)模式都會被直接放在JSON Schema的頂級鍵值對中,便于重用和參考,所有通過 Schema 類進行修改的 子模型 ,比如自定義標題、描述和默認值,都會被遞回地包含,而不是參考,模型的描述從類的檔案字串或 Schema 類的引數描述中獲得,
Schema類以可選的方式提供關于欄位和驗證、引數的額外資訊:
- default:位置引數,因為 Schema 類會替換欄位的默認值,它的第一個引數用來設定默認值,使用 ... 表示這個欄位是必須的,
- alias:欄位的公共名稱,
- title:如果未指定該引數,則使用 field_name.title(),
- description:如果未指定該引數,并且注解是一個子模型,將使用子模型的檔案字串,
- gt:對于數值型的值(int, float, Decimal),在JSON Schema中添加一個 大于 驗證和一個 exclusiveMinimum 注解,
- ge:對于數值型的值(int, float, Decimal),在JSON Schema中添加一個 大于等于 驗證和一個 minimum 注解,
- lt:對于數值型的值(int, float, Decimal),在JSON Schema中添加一個 小于 驗證和一個 exclusiveMaximum 注解,
- le:對于數值型的值(int, float, Decimal),在JSON Schema中添加一個 小于等于 驗證和一個 maximum 注解,
multiple_of:對于數值型的值(int, float, Decimal),在JSON Schema中添加一個 倍數 驗證和一個 multipleOf 注解, - min_length:對于字串型別的值,在JSON Schema中添加一個相應的驗證和 minLength 注釋,
- max_length:對于字串型別的值,在JSON Schema中添加一個相應的驗證和 maxLength 注釋,
- regex:對于字串型別的值,在JSON Schema中添加一個由給定的字串生成的正則運算式驗證和一個 pattern 注解,
- **:任何其他關鍵字引數(例如 example )將會被逐個添加到欄位的模式,
Config 類的 fields 特性代替 Schema 類用以設定以上引數中除過 default 引數的其他引數的值,
默認情況下,模式是使用別名作為鍵生成的,也可以使用模型屬性名而不是使用別名生成:
MainModel.schema/schema_json(by_alias=False)
當有一個等價物可用時,型別、自定義欄位型別和約束(如 max_length)映射到相應的 JSON Schema Core 規范格式,接下來, JSON Schema Validation, OpenAPI Data Types(基于JSON模式)等將以標準的JSON Schema驗證關鍵字 format 為更復雜的 string 型別定義Pydantic子型別擴展,
要查看從Python/Pydantic 到 JSON Schema的欄位模式映射,請參考 欄位模式映射,
您還可以生成一個頂級JSON模式,該JSON模式的 definitions 中只包含一個模型串列及其所有相關子模塊:
import json
from pydantic import BaseModel
from pydantic.schema import schema
class Foo(BaseModel):
a: str = None
class Model(BaseModel):
b: Foo
class Bar(BaseModel):
c: int
top_level_schema = schema([Model, Bar], title='My Schema')
print(json.dumps(top_level_schema, indent=4))
# {
# "title": "My Schema",
# "definitions": {
# "Foo": {
# "title": "Foo",
# "type": "object",
# "properties": {
# "a": {
# "title": "A",
# "type": "string"
# }
# }
# },
# "Model": {
# "title": "Model",
# "type": "object",
# "properties": {
# "b": {
# "$ref": "#/definitions/Foo"
# }
# },
# "required": [
# "b"
# ]
# },
# "Bar": {
# "title": "Bar",
# "type": "object",
# "properties": {
# "c": {
# "title": "C",
# "type": "integer"
# }
# },
# "required": [
# "c"
# ]
# }
# }
# }
您可以自定義生成的 $ref ,$ref 的值的仍然在鍵定義中指定,您仍然可以從鍵定義中獲取鍵值,但是參考將指向你定義的前綴,而不是默認的前綴,
擴展或修改JSON模式的默認定義位置非常有用,例如使用OpenAPI:
import json
from pydantic import BaseModel
from pydantic.schema import schema
class Foo(BaseModel):
a: int
class Model(BaseModel):
a: Foo
top_level_schema = schema([Model], ref_prefix='#/components/schemas/') # Default location for OpenAPI
print(json.dumps(top_level_schema, indent=4))
# {
# "definitions": {
# "Foo": {
# "title": "Foo",
# "type": "object",
# "properties": {
# "a": {
# "title": "A",
# "type": "integer"
# }
# },
# "required": [
# "a"
# ]
# },
# "Model": {
# "title": "Model",
# "type": "object",
# "properties": {
# "a": {
# "$ref": "#/components/schemas/Foo"
# }
# },
# "required": [
# "a"
# ]
# }
# }
# }
錯誤處理
當Pydantic在它正在驗證的資料中發現錯誤時,就會引發 ValidationError 例外,
注意:驗證代碼不應該引發 ValidationError 例外,而應該引發將會被捕獲并用于填充 ValidationError例外的 ValueError 或 TypeError (或其子類)例外,,
無論發現多少錯誤,都只會引發一個例外,ValidationError 將包含關于所有錯誤及其發生方式的資訊,
你可以通過下面幾種方式訪問這些錯誤:
- e.errors():將輸入資料中發現的錯誤作為一個串列回傳,
- e.json():回傳表示
e.errors的JSON, - str(e):將
e.errors以人類可讀的字串回傳,
每一個 error 物件包含以下屬性:
- loc:表示錯誤位置的串列,串列中的第一項是錯誤發生的欄位,后續項將表示子模型在使用時發生錯誤的欄位,
- type:計算機可讀的錯誤的唯一識別符號,
- msg:人類可讀的錯誤的說明,
- ctx:一個可選物件,其中包含呈現錯誤訊息所需的值,
下面的例子展示了錯誤處理的程序:
from typing import List
from pydantic import BaseModel, ValidationError, conint
class Location(BaseModel):
lat = 0.1
lng = 10.1
class Model(BaseModel):
is_required: float
gt_int: conint(gt=42)
list_of_ints: List[int] = None
a_float: float = None
recursive_model: Location = None
data = https://www.cnblogs.com/fengqiang626/p/dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
recursive_model={'lat': 4.2, 'lng': 'New York'},
gt_int=21,
)
try:
Model(**data)
except ValidationError as e:
print(e)
# 5 validation errors
# is_required
# field required (type=value_error.missing)
# gt_int
# ensure this value is greater than 42 (type=value_error.number.not_gt; limit_value=https://www.cnblogs.com/fengqiang626/p/42)
# list_of_ints -> 2
# value is not a valid integer (type=type_error.integer)
# a_float
# value is not a valid float (type=type_error.float)
# recursive_model -> lng
# value is not a valid float (type=type_error.float)
try:
Model(**data)
except ValidationError as e:
print(e.json())
# [
# {
# "loc": [
# "is_required"
# ],
# "msg": "field required",
# "type": "value_error.missing"
# },
# {
# "loc": [
# "gt_int"
# ],
# "msg": "ensure this value is greater than 42",
# "type": "value_error.number.not_gt",
# "ctx": {
# "limit_value": 42
# }
# },
# {
# "loc": [
# "list_of_ints",
# 2
# ],
# "msg": "value is not a valid integer",
# "type": "type_error.integer"
# },
# {
# "loc": [
# "a_float"
# ],
# "msg": "value is not a valid float",
# "type": "type_error.float"
# },
# {
# "loc": [
# "recursive_model",
# "lng"
# ],
# "msg": "value is not a valid float",
# "type": "type_error.float"
# }
# ]
如果你自定義了資料型別或者驗證器,你應該使用 TypeError 和 ValueError 引發錯誤:
from pydantic import BaseModel, ValidationError, validator
class Model(BaseModel):
foo: str
@validator('foo')
def name_must_contain_space(cls, v):
if v != 'bar':
raise ValueError('value must be "bar"')
return v
try:
Model(foo='ber')
except ValidationError as e:
print(e.errors())
# [{'loc': ('foo',), 'msg': 'value must be "bar"', 'type': 'value_error'}]
您還可以定義自己的錯誤類,并且指定錯誤代碼、訊息模板和背景關系:
from pydantic import BaseModel, PydanticValueError, ValidationError, validator
class NotABarError(PydanticValueError):
code = 'not_a_bar'
msg_template = 'value is not "bar", got "{wrong_value}"'
class Model(BaseModel):
foo: str
@validator('foo')
def name_must_contain_space(cls, v):
if v != 'bar':
raise NotABarError(wrong_value=https://www.cnblogs.com/fengqiang626/p/v)
return v
try:
Model(foo='ber')
except ValidationError as e:
print(e.json())
# [
# {
# "loc": [
# "foo"
# ],
# "msg": "value is not \"bar\", got \"ber\"",
# "type": "value_error.not_a_bar",
# "ctx": {
# "wrong_value": "ber"
# }
# }
# ]
datetime型別
Pydantic支持以下的datetime型別:
-
datetime 欄位可以是:
- datetime:已存在的datetime物件
- int或float:假定為自 1970年1月1日的Unix時間,例如,秒(如果小于等于2e10)或毫秒(如果大于2e10)
- str:支持如下格式:
- YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z[±]HH[:]MM]]]
- 表示int或float的字串(假設為Unix時間)
-
date欄位可以是:
- date:已存在的date物件
- int或float:請參考datetime欄位,
- str:支持如下格式:
YYYY-MM-DD- 表示int或float的字串
-
timedelta欄位可以是:
- timedelta:已存在的timedelta物件
- int或float:假定為秒
- str:支持如下格式:
- [HH:MM]SS[.ffffff]
- [[±]P[DD]DT[HH]H[MM]M[SS]S (ISO 8601 格式的 timedelta)
from datetime import date, datetime, time, timedelta
from pydantic import BaseModel
class Model(BaseModel):
d: date = None
dt: datetime = None
t: time = None
td: timedelta = None
m = Model(
d=1966280412345.6789,
dt='2032-04-23T10:20:30.400+02:30',
t=time(4, 8, 16),
td='P3DT12H30M5S'
)
m.dict()
# {'d': datetime.date(2032, 4, 22),
# 'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000, tzinfo=datetime.timezone(datetime.timedelta(seconds=9000))),
# 't': datetime.time(4, 8, 16), 'td': datetime.timedelta(days=3, seconds=45005)}
Exotic型別
Pydantic附帶了許多用于決議或驗證公共物件的實用工具,
import uuid
from decimal import Decimal
from ipaddress import IPv4Address, IPv6Address, IPv4Interface, IPv6Interface, IPv4Network, IPv6Network
from pathlib import Path
from uuid import UUID
from pydantic import (DSN, UUID1, UUID3, UUID4, UUID5, BaseModel, DirectoryPath, EmailStr, FilePath, NameEmail,
NegativeFloat, NegativeInt, PositiveFloat, PositiveInt, PyObject, UrlStr, conbytes, condecimal,
confloat, conint, constr, IPvAnyAddress, IPvAnyInterface, IPvAnyNetwork, SecretStr, SecretBytes)
class Model(BaseModel):
cos_function: PyObject = None
path_to_something: Path = None
path_to_file: FilePath = None
path_to_directory: DirectoryPath = None
short_bytes: conbytes(min_length=2, max_length=10) = None
strip_bytes: conbytes(strip_whitespace=True)
short_str: constr(min_length=2, max_length=10) = None
regex_str: constr(regex='apple (pie|tart|sandwich)') = None
strip_str: constr(strip_whitespace=True)
big_int: conint(gt=1000, lt=1024) = None
mod_int: conint(multiple_of=5) = None
pos_int: PositiveInt = None
neg_int: NegativeInt = None
big_float: confloat(gt=1000, lt=1024) = None
unit_interval: confloat(ge=0, le=1) = None
mod_float: confloat(multiple_of=0.5) = None
pos_float: PositiveFloat = None
neg_float: NegativeFloat = None
email_address: EmailStr = None
email_and_name: NameEmail = None
url: UrlStr = None
password: SecretStr = None
password_bytes: SecretBytes = None
db_name = 'foobar'
db_user = 'postgres'
db_password: str = None
db_host = 'localhost'
db_port = '5432'
db_driver = 'postgres'
db_query: dict = None
dsn: DSN = None
decimal: Decimal = None
decimal_positive: condecimal(gt=0) = None
decimal_negative: condecimal(lt=0) = None
decimal_max_digits_and_places: condecimal(max_digits=2, decimal_places=2) = None
mod_decimal: condecimal(multiple_of=Decimal('0.25')) = None
uuid_any: UUID = None
uuid_v1: UUID1 = None
uuid_v3: UUID3 = None
uuid_v4: UUID4 = None
uuid_v5: UUID5 = None
ipvany: IPvAnyAddress = None
ipv4: IPv4Address = None
ipv6: IPv6Address = None
ip_vany_network: IPvAnyNetwork = None
ip_v4_network: IPv4Network = None
ip_v6_network: IPv6Network = None
ip_vany_interface: IPvAnyInterface = None
ip_v4_interface: IPv4Interface = None
ip_v6_interface: IPv6Interface = None
m = Model(
cos_function='math.cos',
path_to_something='/home',
path_to_file='/home/file.py',
path_to_directory='home/projects',
short_bytes=b'foo',
strip_bytes=b' bar',
short_str='foo',
regex_str='apple pie',
strip_str=' bar',
big_int=1001,
mod_int=155,
pos_int=1,
neg_int=-1,
big_float=1002.1,
mod_float=1.5,
pos_float=2.2,
neg_float=-2.3,
unit_interval=0.5,
email_address='Samuel Colvin <[email protected] >',
email_and_name='Samuel Colvin <[email protected] >',
url='http://example.com',
password='password',
password_bytes=b'password2',
decimal=Decimal('42.24'),
decimal_positive=Decimal('21.12'),
decimal_negative=Decimal('-21.12'),
decimal_max_digits_and_places=Decimal('0.99'),
mod_decimal=Decimal('2.75'),
uuid_any=uuid.uuid4(),
uuid_v1=uuid.uuid1(),
uuid_v3=uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org'),
uuid_v4=uuid.uuid4(),
uuid_v5=uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org'),
ipvany=IPv4Address('192.168.0.1'),
ipv4=IPv4Address('255.255.255.255'),
ipv6=IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
ip_vany_network=IPv4Network('192.168.0.0/24'),
ip_v4_network=IPv4Network('192.168.0.0/24'),
ip_v6_network=IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'),
ip_vany_interface=IPv4Interface('192.168.0.0/24'),
ip_v4_interface=IPv4Interface('192.168.0.0/24'),
ip_v6_interface=IPv6Interface('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128')
)
print(m.dict())
"""
{
'cos_function': <built-in function cos>,
'path_to_something': PosixPath('/home'),
'path_to_file': PosixPath('/home/file.py'),
'path_to_directory': PosixPath('/home/projects'),
'short_bytes': b'foo',
'strip_bytes': b'bar',
'short_str': 'foo',
'regex_str': 'apple pie',
'strip_str': 'bar',
'big_int': 1001,
'mod_int': 155,
'pos_int': 1,
'neg_int': -1,
'big_float': 1002.1,
'mod_float': 1.5,
'pos_float': 2.2,
'neg_float': -2.3,
'unit_interval': 0.5,
'email_address': '[email protected]',
'email_and_name': <NameEmail("Samuel Colvin <[email protected]>")>,
'url': 'http://example.com',
'password': SecretStr('**********'),
'password_bytes': SecretBytes(b'**********'),
...
'dsn': 'postgres://postgres@localhost:5432/foobar',
'decimal': Decimal('42.24'),
'decimal_positive': Decimal('21.12'),
'decimal_negative': Decimal('-21.12'),
'decimal_max_digits_and_places': Decimal('0.99'),
'mod_decimal': Decimal('2.75'),
'uuid_any': UUID('ebcdab58-6eb8-46fb-a190-d07a33e9eac8'),
'uuid_v1': UUID('c96e505c-4c62-11e8-a27c-dca90496b483'),
'uuid_v3': UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e'),
'uuid_v4': UUID('22209f7a-aad1-491c-bb83-ea19b906d210'),
'uuid_v5': UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d'),
'ipvany': IPv4Address('192.168.0.1'),
'ipv4': IPv4Address('255.255.255.255'),
'ipv6': IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
'ip_vany_network': IPv4Network('192.168.0.0/24'),
'ip_v4_network': IPv4Network('192.168.0.0/24'),
'ip_v6_network': IPv4Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'),
'ip_vany_interface': IPv4Interface('192.168.0.0/24'),
'ip_v4_interface': IPv4Interface('192.168.0.0/24'),
'ip_v6_interface': IPv6Interface('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128')
}
"""
欄位也可以是 Callable 型別:
from typing import Callable
from pydantic import BaseModel
class Foo(BaseModel):
callback: Callable[[int], int]
m = Foo(callback=lambda x: x)
print(m)
# Foo callback=<function <lambda> at 0x7f16bc73e1e0>
#警告:Callable 欄位只執行引數是否可呼叫的簡單檢查,不執行引數、引數的型別或回傳型別的驗證,
Secret型別
可以使用 SecretStr 和 SecretBytes 資料型別來存盤您不希望在日志記錄或回溯中可見的敏感資訊,SecretStr 和 SecretBytes 將在轉換為JSON時被格式化為 ********** 或空
from typing import List
from pydantic import BaseModel, SecretStr, SecretBytes, ValidationError
class SimpleModel(BaseModel):
password: SecretStr
password_bytes: SecretBytes
sm = SimpleModel(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')
print(sm)
# SimpleModel password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm.password.get_secret_value())
# IAmSensitive
print(sm.password_bytes.get_secret_value())
b'IAmSensitiveBytes'
try:
SimpleModel(password=[1, 2, 3], password_bytes=[1, 2, 3])
except ValidationError as e:
print(e)
# 2 validation errors
# password
# str type expected (type=type_error.str)
# password_bytes
# byte type expected (type=type_error.bytes)
JSON型別
可以使用JSON資料型別:Pydantic將首先決議原始JSON字串,然后根據定義的JSON結構驗證已決議的物件(如果提供了該物件),
from typing import List
from pydantic import BaseModel, Json, ValidationError
class SimpleJsonModel(BaseModel):
json_obj: Json
class ComplexJsonModel(BaseModel):
json_obj: Json[List[int]]
SimpleJsonModel(json_obj='{"b": 1}')
# <SimpleJsonModel json_obj={'b': 1}>
ComplexJsonModel(json_obj='[1, 2, 3]')
# <ComplexJsonModel json_obj=[1, 2, 3]>
try:
ComplexJsonModel(json_obj=12)
except ValidationError as e:
print(e)
# 1 validation error
# json_obj
# JSON object must be str, bytes or bytearray (type=type_error.json)
try:
ComplexJsonModel(json_obj='[a, b]')
except ValidationError as e:
print(e)
# 1 validation error
# json_obj
# Invalid JSON (type=value_error.json)
try:
ComplexJsonModel(json_obj='["a", "b"]')
except ValidationError as e:
print(e)
# 2 validation errors
# json_obj -> 0
# value is not a valid integer (type=type_error.integer)
# json_obj -> 1
# value is not a valid integer (type=type_error.integer)
自定義資料型別
你也可以定義你自己的資料型別,類方法 __get_validators__ 將會被呼叫,用以獲取驗證器來決議和驗證資料,
注意:從v0.17 開始,__get_validators__ 變更成 get_validators,原來的名稱仍然可以使用,但是在將來的版本中可能會被移除,
from pydantic import BaseModel, ValidationError
class StrictStr(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if not isinstance(v, str):
raise ValueError(f'strict string: str expected not {type(v)}')
return v
class Model(BaseModel):
s: StrictStr
Model(s='hello world')
# <Model s='hello world'>
try:
print(Model(s=123))
except ValidationError as e:
print(e.json())
# [
# {
# "loc": [
# "s"
# ],
# "msg": "strict string: str expected not <class 'int'>",
# "type": "value_error"
# }
# ]
幫助函式
Pydantic在模型中提供了3個幫助函式來決議資料,這三個幫助函式都是類方法,
- parse_obj:這個方法幾乎與模型的 init 方法相同,除過當傳遞的物件不是 dict,將會引發 ValidationError(而不是Python引發 TypeError),
- parse_raw:接收一個字串或位元組物件并決議成JSON,或者使用 pickle 將其反序列化然后傳遞給 parse_obj 方法,傳遞的資料的型別通過 content_type 引數來推斷,否則假定為JSON,
- parse_file:讀取檔案并將其內容傳遞給 parse_raw 方法,如果未指定 content_type 引數的值,將會由檔案拓展名來推斷檔案內容所表示的Python物件的型別,
import pickle
from datetime import datetime
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: datetime = None
m = User.parse_obj({'id': 123, 'name': 'bob'})
# <User id=123 name='bob' signup_ts=None>
try:
User.parse_obj(['not', 'a', 'dict'])
except ValidationError as e:
print(e)
# 1 validation error
# __obj__
# User expected dict not list (type=type_error)
m = User.parse_raw('{"id": 123, "name": "James"}')
# <User id=123 name='James' signup_ts=None>
# pickle_data = https://www.cnblogs.com/fengqiang626/p/pickle.dumps({'id': 123, 'name': 'James', 'signup_ts': datetime(2017, 7, 14)})
m = User.parse_raw(pickle_data, content_type='application/pickle', allow_pickle=True)
# <User id=123 name='James' signup_ts=datetime.datetime(2017, 7, 14, 0, 0)>
# 注意:pickle 允許對復雜物件進行編碼,要使用它,需要顯式地將決議函式的 allow_pickle 引數的值設定為 True,
模型的 Config 類
Pydantic的行為可以通過模型中的 Config 類來控制,
Config 類包含如下一些類屬性:
- anystr_strip_whitespace:是否消除字串或者位元組的前導和后置空白空格,默認為 False,
- min_anystr_length:字串或位元組的最小長度,默認為0
- max_anystr_length:字串或位元組的最大長度,默認為 2 ** 16
- validate_all:是否驗證欄位的默認值,默認為 False,
- extra:是否忽略、允許或禁止模型中的額外屬性,可以使用字串值 ignor,allow或 forbid,也可以使用 Extra 的列舉值,默認值是 Extra.ignore,
- allow_mutation:模型是否為偽不可變型別,默認值為 True,
- use_enum_values:是否使用列舉的 value 特性填充模型,而不是使用原始列舉,這在您希望序列化 model.dict() 時非常有用,默認值為 False,
- fields:每個欄位的模式資訊,這等價于使用 schema 類,默人在 None,
- validate_assignment:是否在為屬性賦值時執行驗證,默認為 False,
- allow_population_by_alias:是否可以按照模型屬性(而不是嚴格的別名)給出的名稱填充別名欄位;在啟用此功能之前,請務必閱讀下面的警告,默認值為 False,
error_msg_templates:用于重寫默認的錯誤訊息模版,傳入一個字典,其中的鍵與要覆寫的錯誤訊息匹配,默認值為空的字典, - arbitrary_types_allowed:是否允許欄位使用任意的用戶自定義資料型別(只需檢查值是否是該型別的實體即可驗證它們),如果該引數值為 False,在模型宣告中使用自定義的資料型別將會引發 RuntimeError 例外,默認值為 False,
- json_encoders:定制將型別編碼為JSON的方式,請參閱JSON序列化了解更多細節,
警告:在啟用 allow_population_by_alias 之前請三思!啟用它可能會導致先前正確的代碼變得不正確,例如,假設您有一個名為 card_number 的欄位和別名 cardNumber,在 allow_population_by_alias 屬性值為 False 時,只使用鍵 card_number 嘗試決議物件將會失敗,但是,如果 allow_population_by_alias 屬性值設定為 True,那么現在可以從 cardNumber 或 card_number 填充 card_number 欄位,并且先前無效的示例物件現在將是有效的,對于某些用例,這可能是需要的,但是在其他用例中(比如這里給出的例子),放松對別名的嚴格限制可能會引入bug,
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
v: str
class Config:
max_anystr_length = 10
error_msg_templates = {'value_error.any_str.max_length': 'max_length:{limit_value}'}
try:
Model(v='x' * 20)
except ValidationError as e:
print(e)
# 1 validation error
# v
# max_length:10 (type=value_error.any_str.max_length; limit_value=https://www.cnblogs.com/fengqiang626/p/10)
基于 @dataclass 裝飾器版本的模型:
from datetime import datetime
from pydantic import ValidationError
from pydantic.dataclasses import dataclass
class MyConfig:
max_anystr_length = 10
validate_assignment = True
error_msg_templates = {
'value_error.any_str.max_length': 'max_length:{limit_value}',
}
@dataclass(config=MyConfig)
class User:
id: int
name: str = 'John Doe'
signup_ts: datetime = None
user = User(id='42', signup_ts='2032-06-21T12:00')
try:
user.name = 'x' * 20
except ValidationError as e:
print(e)
# 1 validation error
# name
# max_length:10 (type=value_error.any_str.max_length; limit_value=https://www.cnblogs.com/fengqiang626/p/10)
設定
Pydantic最有用的應用之一是定義默認設定,并允許它們被環境變數或關鍵字引數覆寫(例如在單元測驗中),
from typing import Set
from pydantic import BaseModel, DSN, BaseSettings, PyObject
class SubModel(BaseModel):
foo = 'bar'
apple = 1
class Settings(BaseSettings):
redis_host = 'localhost'
redis_port = 6379
redis_database = 0
redis_password: str = None
auth_key: str = ...
invoicing_cls: PyObject = 'path.to.Invoice'
db_name = 'foobar'
db_user = 'postgres'
db_password: str = None
db_host = 'localhost'
db_port = '5432'
db_driver = 'postgres'
db_query: dict = None
dsn: DSN = None
# to override domains:
# export MY_PREFIX_DOMAINS = '["foo.com", "bar.com"]'
domains: Set[str] = set()
# to override more_settings:
# export MY_PREFIX_MORE_SETTINGS = '{"foo": "x", "apple": 1}'
more_settings: SubModel = SubModel()
class Config:
env_prefix = 'MY_PREFIX_' # defaults to 'APP_'
fields = {
'auth_key': {
'alias': 'my_api_key'
}
}
這里的 redis_port 可以通過 export MY_PREFIX_REDIS_PORT=6380 修改,auth_key 可以通過 exportmy_api_key=6380 修改,
默認情況下,BaseSettings 按照以下優先級來獲取欄位值(其中3的優先級最高,并會重寫其他兩項):
-
Settings 中設定的值,
-
環境變數中設定的值,例如上面的
MY_PREFIX_REDIS_PORT, -
初始化
Settings類時傳遞的引數,
可以通過重寫 BaseSettings 類的 _build_values 方法來更改這個默認行為,
復雜的型別,如 list,dict,set 和子模型可以通過JSON環境變數來設定,
可以以不區分大小寫的方式讀取環境變數:
from pydantic import BaseSettings
class Settings(BaseSettings):
redis_host = 'localhost'
class Config:
case_insensitive = True
這里的 redis_port 可以通過 export APP_REDIS_HOST, export app_redis_host 或 export app_REDIS_host 修改,
動態模型創建
在某些情況下,模型的形狀直到運行時才知道,由于這個原因,Pydantic提供 create_model 方法來允許動態創建模型,
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model('DynamicFoobarModel', foo=(str, ...), bar=123)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
這里,DynamicFoobarModel 和 StaticFoobarModel 是完全相同的,
欄位要么由表單的一個元組 (<type>, <default value>) 定義,要么僅由一個默認值定義,特殊的關鍵字引數 __config__ 和 __base__ 可以用來定制新模型,這包括使用額外的欄位擴展基本模型,
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 120
BarModel = create_model('BarModel', apple='russet', banana='yellow', __base__=FooModel)
# <class 'BarModel'>
# ', '.join(BarModel.__fields__.keys())
# 'foo, bar, apple, banana'
與mypy一起使用
Pydantic和mypy一起作業,可以讓你使用所需變數的 僅注釋 版本,
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, NoneStr
class Model(BaseModel):
age: int
first_name = 'John'
last_name: NoneStr = None
signup_ts: Optional[datetime] = None
list_of_ints: List[int]
m = Model(age=42, list_of_ints=[1, '2', b'3'])
try:
Model()
except ValidationError as e:
print(e)
# 2 validation errors
# age
# field required (type=value_error.missing)
也可以通過mypy以如下方式運行:
mypy --ignore-missing-imports --follow-imports=skip --strict-optional pydantic_mypy_test.py
嚴格的可選項
要給代碼傳遞 --strict-optional 選項,需要對所有沒有預設值的欄位使用 Optional[] 或 Optional[] 的別名,這是mypy的標準做法,
Pydantic提供了一些有用的可選或聯合型別:
- NoneStr 又叫做 Optional[str]
- NoneBytes 又叫做 Optional[bytes]
- StrBytes 又叫做 Union[str, bytes]
- NoneStrBytes 又叫做 Optional[StrBytes]
如果這些還不夠,你可以定義,
必須欄位和mypy
省略號符號 … 不能與mypy一同作業,您需要像上面的示例那樣使用注釋欄位,
警告:請注意,僅使用注釋欄位將更改元資料中欄位的順序和錯誤:始終以僅有注釋的欄位優先,但仍然按照欄位定義的順序,
要解決這個問題,可以使用 Required(通過 pydantic import Required)欄位作為僅使用省略號或注釋的欄位的別名,
偽不可變性
可以通過 allow_mutation = False 將模型配置為不可變的,這將防止更改模型的屬性,
警告:Python中的不變性從來都不是嚴格的,如果開發人員意志堅定/愚蠢,他們總是可以修改所謂的 不可變 物件,
from pydantic import BaseModel
class FooBarModel(BaseModel):
a: str
b: dict
class Config:
allow_mutation = False
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except TypeError as e:
print(e)
試圖更改 a 導致了一個錯誤,并且它仍然保持不變,但是字典物件 b 是可變的,foobar 的不變性不會阻止 b 的值的更改,
復制
dict 方法回傳一個包含模型屬性的字典,子模型被遞回地轉換為字典, copy 方法允許模型被復制,這對于不可變模型特別有用,
dict、copy 和 json 方法(如下所述)都使用可選的 include 和 exclude 關鍵字引數來控制回傳或復制哪些屬性,copy 方法接受額外的關鍵字引數 update,它接受將屬性映射到新值的型別為字典物件的值,這些新值將在復制模型并進行深度復制時應用,
dict 和 json 采用可選的 skip_defaults 關鍵字引數,該引數將跳過未顯式設定的屬性,這對于減少具有許多不經常更改的默認欄位的模型的序列化大小非常有用,
from pydantic import BaseModel
class BarModel(BaseModel):
whatever: int
class FooBarModel(BaseModel):
banana: float
foo: str
bar: BarModel
m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 456})
m.dict()
# {'banana': 3.14, 'foo': 'hello', 'bar': {'whatever': 456}}
m.dict(include={'foo', 'bar'})
# {'foo': 'hello', 'bar': {'whatever': 456}}
m.dict(exclude={'foo', 'bar'})
# {'banana': 3.14}
m.copy()
# <FooBarModel banana=3.14 foo='hello' bar=<BarModel whatever=456>>
m.copy(include={'foo', 'bar'})
# <FooBarModel foo='hello' bar=<BarModel whatever=456>>
m.copy(exclude={'foo', 'bar'})
# <FooBarModel banana=3.14>
m.copy(update={'banana': 0})
# <FooBarModel banana=0 foo='hello' bar=<BarModel whatever=456>>
id(m.bar), id(m.copy().bar)
# (4417630280, 4417630280)
id(m.bar), id(m.copy(deep=True).bar)
# (4417630280, 4417630928)
序列化
Pydantic支持將資料序列化為JSON和Pickle,當然可以通過處理 dict() 方法的結果將資料序列化為您喜歡的任何其他格式,
JSON序列化
json()方法將模型序列化為JSON,然后 json() 方法呼叫 dict() 方法并序列化其結果,
可以通過在模型上使用 json_encoders 配置屬性定制序列化,鍵應該是型別,值應該是序列化該型別的函式,參見下面的示例,
如果這還不夠,json() 方法接受一個可選的 encoder 引數,該引數允許完全控制如何將非標準型別編碼為JSON,
from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat
class BarModel(BaseModel):
whatever: int
class FooBarModel(BaseModel):
foo: datetime
bar: BarModel
m = FooBarModel(foo=datetime(2032, 6, 1, 12, 13, 14), bar={'whatever': 400})
m.json()
# '{"foo": "2032-06-01T12:13:14", "bar": {"whatever": 400}}'
class WithCustomEncoders(BaseModel):
dt: datetime
diff: timedelta
class Config:
json_encoders = {
datetime: lambda v: (v - datetime(1970, 1, 1)).total_seconds(),
timedelta: timedelta_isoformat
}
m = WithCustomEncoders(dt=datetime(2032, 6, 1), diff=timedelta(hours=100))
m.json()
# '{"dt": 1969660800.0, "diff": "P4DT4H0M0.000000S"}'
默認情況下,時間增量被編碼為一個簡單的浮點數,以總秒為單位,timedelta_isoformat 作為一個可選選項提供,它實作了ISO 8601時間差異編碼,
Pickle序列化
使用與 copy() 相同的管道,Pydantic支持有效的 pickle 和 unpick,
import pickle
from pydantic import BaseModel
class FooBarModel(BaseModel):
a: str
b: int
m = FooBarModel(a='hello', b=100)
# <FooBarModel a='hello' b=100>
data = https://www.cnblogs.com/fengqiang626/p/pickle.dumps(m)
# b'\x80\x03c__main__\nFooBarModel\nq\x00)\x81q\x01}q\x02(X\n\x00\x00\x00__values__q\x03}q\x04(X\x01\x00\x00\x00aq\x05X\x05\x00\x00\x00helloq\x06X\x01\x00\x00\x00bq\x07KduX\x0e\x00\x00\x00__fields_set__q\x08cbuiltins\nset\nq\t]q\n(h\x07h\x05e\x85q\x0bRq\x0cub.'
m2 = pickle.loads(data)
# <FooBarModel a='hello' b=100>
抽象基類
Pydantic模型可以與Python的抽象基類(ABC)一起使用,
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
延遲注解
注意:通過 future 匯入和 ForwardRef 的延遲注解都需要Python 3.7+,
全域延遲注解在Pydantic中應該 只是能作業,
from __future__ import annotations
from typing import List
from pydantic import BaseModel
class Model(BaseModel):
a: List[int]
Model(a=('1', 2, 3))
# <Model a=[1, 2, 3]>
在內部,Pydantic將呼叫類似于 typing.get_type_hints 的方法用于決議注釋,
要使用 ForwardRef ,您可能需要在創建模型之后呼叫 model .update_forward_refs(),這是因為在下面的示例中,Foo 在創建之前并不存在(顯然),所以 ForwardRef 不能首先被決議,您必須等到 Foo 創建之后,然后呼叫 update_forward_refs 來正確地設定型別,然后才能使用模型,
from typing import ForwardRef
from pydantic import BaseModel
Foo = ForwardRef('Foo')
class Foo(BaseModel):
a: int = 123
b: Foo = None
Foo.update_forward_refs()
Foo()
# <Foo a=123 b=None>
Foo(b={'a': '321'})
# <Foo a=123 b=<Foo a=321 b=None>>
# 警告:要將字串(型別名稱)決議成注解(型別),pydantic需要查找 moduel.__dict__ ,就像 get_type_hints 所做的一樣 ,這意味著pydantic不能很好地處理模塊全域范圍內未定義的型別,
# 例如,這樣可以正常作業:
from __future__ import annotations
from typing import List # <-- List is defined in the module's global scope
from pydantic import BaseModel
def this_works():
class Model(BaseModel):
a: List[int]
print(Model(a=(1, 2)))
# 但是這樣將不能:
from __future__ import annotations
from pydantic import BaseModel
def this_is_broken():
from typing import List # <-- List is defined inside the function so is not in the module's global scope
class Model(BaseModel):
a: List[int]
print(Model(a=(1, 2)))
# 解決這個問題超出了pydantic的呼叫:要么洗掉 future 的匯入,要么全域地宣告型別,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/85038.html
標籤:Python
上一篇:96不同的二查搜索樹
下一篇:136只出現一次的數字
