Pydantic用type hint实现数据验证, 2023-05-07

(2023.05.07 Sun @KLN HK)
几个基本概念:

  • runtime
    有多种含义,1) 用于实现编程语言执行模块的一段代码,该代码允许程序和硬件做互动,往往是编程语言的一部分,不需要单独安装;2) 程序运行的时间,是程序lifecycle的一个阶段,It is the time that a program is running alongside all the external instructions needed for proper execution. runtime有时用于区分于编译,后者称为compile time。
  • type hint
    Python是动态类型语言(dynamically-typed language),编译器只在代码运行时执行类型检测,也因此变量类型可随着运行运行而改变。Type hints在PEP 484被引入到Python中,将静态类型检测的特性带到了代码分析中。Type hint使用Python annotations实现,将类型赋给变量、参数、函数输入、返回值类型、类属性、方法。加入type hints不会耗费runtime。

type hint优势

  1. 提供了对代码的文档注释,包括函数输入的类型和返回类型,类方法的相关信息
  2. 提高代码可读性、可编辑、可修改性
  3. 构建和保持一个清晰干净的架构

当然type hint也有劣势

  1. 占用更多开发时间
  2. 启动时间变长,特别是如果使用typing包,在短脚本中启动导入的时间显著提升
  3. 对早期没有引入Python annotations版本的并无显著的type hint效果

Pydantic

pydantic是使用python type hints的数据验证工具。一个典型的使用流程是预先定义data model中的字段和类型、是否required等信息,并将输入的数据,如JSON,转化为该data model。如果输入数据与data model中的定义有偏差,则转化时将报错,这样就实现了类静态类型检测的特性。Pydantic常见于FastAPI的应用。

案例

# python 3.9
from datetime import datetime
from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: list[int] = []

external_data = {
    'id': '123',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}

>> user = User(**external_data)
>> user
User(id=123, signup_ts=datetime.datetime(2019, 6, 1, 12, 22), friends=[1, 2, 3], name='John Doe')
>> print(user.id)
123
>> print(repr(user.signup_ts))
datetime.datetime(2019, 6, 1, 12, 22)
>> print(user.friends)
[1, 2, 3]
>> print(user.dict())
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""

定义data model,继承自pydantic的BaseModel,并在子类中定义变量和类型、默认值。使用typing.Optional方法,表明该变量并非required变量,当用户定义时不提供该变量值则默认为None。另外也可以从typing中引入Union方法,该方法中标识出的类型是数据可能定义的类型,形式如

a: Union[<data_type1>, <data_type2>, ...]

from typing import Union
from pydantic import BaseModel

class test(BaseMode):
    ua: Union[int, str]  # this variable could be int or str

对比OptionalUnion,可以看到Optional[<data_type>]相当于Union[<data_type>, None]

注意到在Python 3.10 and beyond,Optional可以用|代替。前面的代码在3.10 and beyond可以写成

from datetime import datetime
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime | None = None
    friends: list[int] = []

Model的built-in method

(2023.06.04 Sun KLN HK)
实例化一个model之后,找到内置方法大致如下

'construct', 'copy', 'dict', 'from_orm', 'json', 'parse_file', 'parse_obj', 
'parse_raw', 'schema', 'schema_json', 'update_forward_refs'
  • dict方法:用于将实例化的对象转变为dict形式
  • json方法:用于将对象转变为json格式
>> class User(BaseModel):
...     id: int
...     name="J D"
>> u1 = User(id='123')
>> u1.dict()
{'id': 123, 'name': 'J D'}
>> u1.json()
'{"id": 123, "name": "J D"}'
>> u1.schema()
{'title': 'User', 'type': 'object', 'properties': {'id': {'title': 'Id', 'type': 'integer'}, 
'name': {'title': 'Name', 'default': 'J D', 'type': 'string'}}, 'required': ['id']}

ValidationError

pydantic提供了数据验证返回的错误类型ValidationError,一旦提供的数据与定义的data model不匹配,则返回该错误。

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": "value_error.datetime"
  },
  {
    "loc": [
      "friends",
      2
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  }
]

pydantic和Enum

(2023.06.17 Sat @SZ 颖隆大厦)
pydantic通过在model中指定字段可以实现对数据字段的限制。而python的enum结合pydantic使用可以实现对特定字段中可选值的限制,将指定字段的值限制在预先定义的集合中,数据初始化中一旦赋予该字段的值超出预定集合,则初始化将会失败。同时,通过enum的使用,结合FastAPI等工具,可实现下拉列表功能。

在下面案例中,定义宠物的信息,宠物model Pet包括namesextypes三个字段,其中sex可选值只有malefemaletypes可选值仅包含常见的几种驯化动物。sextypes的枚举类型都有Enum的衍生类来定义。

from enum import Enum
from pydantic import BaseModel

class Sex(Enum):
    MALE = "male"
    FEMALE = "female"

class DomesticAnimal(Enum):
    BIRD = "bird"
    CAT = "cat"
    DOG = "dog"
    TURTLE = "turtle"
    FISH = "fish"

class Pet(BaseModel):
    name: str
    sex: Sex
    types: DomesticAnimal

可以使用两种方式定义一个Pet对象。

>> a1 = {'name': 'Cathy', 'sex': 'female', 'types': 'dog'}
>> ca = Pet(**a1)
# or
>> ca = Pet(name='Cathy', sex='female', types='dog')
>> ca
Pet(name='Cathy', sex=<Sex.FEMALE: 'female'>, types=<DomesticAnimal.DOG: 'dog'>)

看到ca对象的结果正常显示。如果在定义时,将其中的types改成没有在DomesticAnimal中定义的种类,则会返回错误。

>> cheetah = Pet(name="jizhitangjiang", sex="male", types="cheetah")
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
/var/folders/d1/s0h2kl014l1bkb0_q0_g5vpr0000gn/T/ipykernel_85550/2575694669.py in <module>
----> 1 cheetah = Pet(name="jizhitangjiang", sex="male", types="cheetah")

~/opt/anaconda3/lib/python3.9/site-packages/pydantic/main.cpython-39-darwin.so in pydantic.main.BaseModel.__init__()

ValidationError: 1 validation error for Pet
types
  value is not a valid enumeration member; permitted: 'bird', 'cat', 'dog', 'turtle', 'fish' (type=type_error.enum; enum_values=[<DomesticAnimal.BIRD: 'bird'>, <DomesticAnimal.CAT: 'cat'>, <DomesticAnimal.DOG: 'dog'>, <DomesticAnimal.TURTLE: 'turtle'>, <DomesticAnimal.FISH: 'fish'>])

Error message中显示初始化的赋值超出可选范围。

pydantic的BaseModel结合python Enum可实现对指定字段的可选值的选择。

Model中的字段多重命名和显示

(2023.09.24 Sun, 10.14 Sat @KLN)
pydantic的Field方法中,可定义参数alias指定字段的多重命名。

from pydantic import BaseModel, Field
# pydantic.__version__ = 2.3

class test1(BaseModel):
    name: str = Field(alias="id")
    gender: str = Field(alias="sex")
    class Config:
        populate_by_name = True


c1 = {"name": "J", "gender": "m"}
c2 = {"id": "jolin", "sex": "f"}
>> test1(**c1)
test1(name='J', gender='m')
>> test1(**c2)
test1(name='jolin', gender='f')

在使用多重命名时,注意到test1这个model中加入了Config子类,并设定populate_by_name字段为True,这个设定保证了model被赋值/实例化过程中可接受字段的名字和alias,如果没有设定populate_by_name字段,则只接受alias的字段名。

注意这里pydantic的版本是2.3。而当前(2023年10月)fastapi的0.85.1版本支持1.10.13版本的pydantic。

Reference

1 pydantic 点 dev

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容