Pydantic 是一个 Python 的数据验证和模型声明库,旨在建立正确使用对象化方法的简单,快速,和可扩展方式。
它可以省去你把琐碎数据验证任务,保证你的数据总是整洁有序。Pydantic为你提供一种更优雅的方式完成你的任务,使你可以专注于开发解决方案,而不是验证数据的任务。
Pydantic可以用来定义模型,以便于格式化数据并转换成Python数据对象。它也可以在输入和输出之间执行类型转换,并根据你定义的规则进行数据验证。
你可以按照你的需要定义模型,你可以快速创建精确和可扩展的域,包括验证数据格式,类型,值和翻译字段。你可以在域之间建立灵活性,允许交叉验证和明智的行为定义。
Pydantic还允许自定义验证器,装饰器和hooks,并且可以与其他任何使用python的模型框架胶接,这使你可以根据自己的需要创造多层结构。
Pydantic有众多的优点,既可用于大型项目,也可用于简单项目。一个重要的一点,它可以很容易地扩展,也可以允许简单的复制/粘贴套件,从而节省很多时间。此外,所有的数据和关系都能被使用者轻松识别。
总的来说,Pydantic可以让你省去大量时间,以更简单的方式进行数据验证,这样你就可以将更多的精力放在实现自己的想法上
简单上手
from pydantic import BaseModel
class Index(BaseModel):
id: int | None # 表示id这个属性为int类型,如果不符合会强制转换为该类型,默认是None,可以不传
type_number: str | None # 表示type_number属性为str类型,如果不符合会强制转换为该类型,默认None,可以不传
fruit: list # 表示fruit属性为列表,如果不符合会强制转化,默认不为None,为必须传入的参数
name = 'jason' # name属性有个默认值,如果不传入使用默认,传入则更改
下来我们来针对上面的代码,进行测试
必要参数与默认None参数
# type_number默认为None,不传入不会报错
obj = Index(id=1, fruit=['苹果', '香蕉'])
print(obj.id) # 1
print(type(obj.id)) # <class 'int'>
print(obj.fruit) # ['苹果', '香蕉']
print(type(obj.fruit)) # <class 'list'>
# fruit默认不为None,不传入会报错
obj = Index(id=1, type_number='9527')
# 报错信息,提示的很明显,fruit参数必传
fruit
field required (type=value_error.missing)
传入与预期类型不同的参数
# id预期是int,我们传入str
obj = Index(id='3', fruit=['苹果'])
print(obj.id) # 3
print(type(obj.id)) # <class 'int'>
print(obj.fruit) # ['苹果', '香蕉']
print(type(obj.fruit)) # <class 'list'>
这个时候我们发现,当我们传入的是字符串类型的时候,当我们产生属性的时候,会直接将字符串转为我们期望的int类型,进行类型强转,但是如果是无法转换的会报错,如下
obj = Index(id='a', fruit=['苹果'])
# 报错,会直接告诉我们是哪个参数错误,错误的类型
id
value is not a valid integer (type=type_error.integer)
针对列表内的数据类型也是可以做限制的,如下
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: List[str] | None 入的参数 # 这里我们限制了list内部的类型必须为str
name = 'jason'
obj = Index(id='1', fruit=['苹果', 1])
print(obj.id) # 3
print(type(obj.id)) # <class 'int'>
print(obj.fruit) # ['苹果', '1']
print(type(obj.fruit)) # <class 'list'>
我们可以看到在上面的代码中,我们传入的list内部参数是str与int,但是当我们真正打印出来的时候列表内的int已经被转换为了str
关于类型转换
当我们上述操作进行的时候,我想到了我们声明了数据类型,然后如果不一致转换,那我们有没有办法自己进行数据类型的转换呢,我查看到pydantic中的validator装饰器,可以对单个数据值进行处理,下面我尝试了一下
from pydantic import BaseModel, validator
from typing import List
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: List[str] | None
name = 'jason'
@validator('fruit')
@classmethod
def convert_type(cls, value):
print('触发')
if value is None:
return None
if not isinstance(value, list):
try:
value = [value]
except Exception:
raise TypeError('传入的类型错误')
return value
obj = Index(id=1, fruit='苹果')
# 会直接报错,报错内容如下,证明还是我们的参数错误
fruit
value is not a valid list (type=type_error.list)
当我按照我的想法尝试的时候,发现一个问题,当我们传入的参数并不是我们预期想要要求的参数,他并没有执行'触发的打印操作',会直接报错,不会走我们写的类型转化的参数,当我做以下修改的时候他就可以进行类型的转化
from pydantic import BaseModel, validator
from typing import List,Union
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: Union[List[str], str] | None # 类型注释改为允许str或List[str]
name = 'jason'
@validator('fruit')
@classmethod
def convert_type(cls, value):
print('触发') # 打印了
if value is None:
return None
if isinstance(value, str):
return [value]
return value
# id预期是int,我们传入str
obj = Index(id=1, fruit='苹果')
print(obj.id) # 3
print(type(obj.id)) # <class 'int'>
print(obj.fruit) # ['苹果']
print(type(obj.fruit)) # <class 'list'>
这个时候我们发现,是调用了我们写的函数,进行类型转换,我们主要在声明的时候进行了一个修改,我们对于fruit的属性声明修改为了可以接收list或者str,所以我们可以得到一个结论pydantic对于属性类型的转换是优先于validator装饰的函数,这样对于传入的类型参数限制会更好一点
在上面我代码中我们使用Union:
Union
是Python中typing模块中的一种类型,用于声明多个可能的类型。它允许您指定一个变量可以具有多种类型中的任何一种
如果您希望一个变量可以是整数或字符串类型,您可以使用Union[int, str]
来声明该变量的类型。这意味着该变量可以接受整数或字符串的值
我们除了Union还可以使用Any:
Any
是 Python 中的一种特殊类型注解,表示可以是任何类型的对象。在类型注解中使用 Any
表示该变量可以接受任意类型的值,相当于取消了类型检查。使用 Any
类型注解的变量可以接受任意类型的值,不会触发类型错误
validator其他用法
刚才我们使用了pydantic中的validator进行数据值的校验,我们进行了单个值,其实我们还是可以进行多个值的校验
from pydantic import BaseModel, validator
from typing import List, Union
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: Union[List[str], str] | None # 类型注释改为允许str或List[str]
name = 'jason'
@validator('fruit', 'id', )
@classmethod
def convert_type(cls, value):
print(value)
print('触发') # 打印了
if value is None:
return None
if isinstance(value, str):
return [value]
return value
# id预期是int,我们传入str
obj = Index(id=1, fruit='苹果')
print(obj.id)
print(type(obj.id))
print(obj.fruit)
print(type(obj.fruit))
下面是打印信息
1 这里是id的值
触发
苹果 这里是fruit的值
触发
1
<class 'int'>
['苹果']
<class 'list'>
我们查看打印信息,当我们使用validator进行多个属性值的校验的时候, 他会把validator括号中的值按照我们类中定义的属性的顺序来进行调用函数校验,因为我故意把装饰器中的参数顺序写反但是还是会优先打印id的值,如果我再修改代码
from pydantic import BaseModel, validator
from typing import List, Union
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: List[str] | None
# fruit: Union[List[str], str] | None # 类型注释改为允许str或List[str]
name = 'jason'
@validator('fruit', 'id', )
@classmethod
def convert_type(cls, value):
print(value)
print('触发') # 打印了
if value is None:
return None
if isinstance(value, str):
return [value]
return value
# id预期是int,我们传入str
obj = Index(id=1, fruit='苹果')
print(obj.id)
print(type(obj.id))
print(obj.fruit)
print(type(obj.fruit))
打印信息
1
触发File "pydantic\main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Index
fruit
value is not a valid list (type=type_error.list)
通过这两步我们可以发现,其实pydantic他的validator校验其实还是按照产生的顺序来进行的,我们上面的例子,他首先会校验id值是否符合预期,符合预期再调用我们的validator装饰函数校验,然后再会进行fruit属性值的校验,所以打印才会打印id的值,然后打印触发,当fruit的属性值就会不符合预期直接报错
root_validator装饰器
简单用法
from pydantic import BaseModel, validator, root_validator
from typing import List, Union
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: Union[List[str], str] | None # 类型注释改为允许str或List[str]
name = 'jason'
@root_validator
@classmethod
def conver_value(cls, value):
print(value, 'conver_value')
# {'id': 1, 'type_number': None, 'fruit': ['苹果'], 'name': 'jason'} conver_value
print(type(value))
# <class 'dict'>
print(value.get('id'))
# 1
return value
obj = Index(id=1, fruit='苹果')
我们可以看到当我们使用root_validator装饰以后,我们的校验函数中的value值其实就是我们传入的所有参数,这个参数的类型是一个字典,所以我们直接使用字典的get方法就可以取到我们传入的属性值,然后对于该值进行一系列的校验
那关于validator和root_validator的执行顺序应该是什么样的呢,我们也可以通过代码试出来
from pydantic import BaseModel, validator, root_validator
from typing import List, Union
class Index(BaseModel):
id: int | None
type_number: str | None
fruit: Union[List[str], str] | None # 类型注释改为允许str或List[str]
name = 'jason'
@validator('fruit', 'id', )
@classmethod
def convert_type(cls, value):
print(type(value))
print('触发convert_type')
if value is None:
return None
if isinstance(value, str):
return [value]
return value
@root_validator
@classmethod
def conver_value(cls, value):
print('触发conver_value')
print(value)
return value
obj = Index(id=1, fruit='苹果')
我们现在来看打印信息就可以看到他们的执行顺序了
<class 'int'>
触发convert_type
<class 'str'>
触发convert_type
{'id': 1, 'type_number': None, 'fruit': ['苹果'], 'name': 'jason'}
触发conver_value
我们可以看到因为validator两个参数所以他打印了两次,而且我们也可以通过fruit的值来看到再conver_value函数中他的值已经是列表了,所以我们可以得出一个结论
执行顺序
1.先执行pydactic的类型校验,如果不符合类型进行强制转换,无法转换则会报错
2.执行validator装饰的函数,validator装饰器中的参数要定义为我们的属性值,这里拿到的value已经是符合我们预期的数据类型了
3.执行root_validator装饰的函数,这里的value是经过上述两步操作之后的数据,他是一个字典的形式,键是我们定义的属性名,值是我们传入的属性值