python中pydantic库

pydantic库详解

一、 概述

1、 简介

该库的官方文档位置为:https://pydantic-docs.helpmanual.io/

不知道大家是否非常羡慕C语言等在进行函数传参时,可以指定数据类型来传参呢?

我之前有一篇讲过使用typing来指定数据类型,但是其仅仅是能指定数据类型,只能做一个提醒的作用,那么我们如何来结合typing模块,来写一个可以像Java等语言的指定参数类型呢?这里我推荐pydantic库。

首先,在学这个库之前,我们需要去回顾一下typing库的使用方法:https://blog.csdn.net/qq_62789540/article/details/124790174

然后,我们来解释一下我们即将要学的库:

其使用 Python 类型注释的数据验证和设置管理。

pydantic在运行时强制执行类型提示,并在数据无效时提供用户友好的错误。

定义数据应该如何在纯的、规范的 Python 中保存;用pydantic验证它。

2、 优势

所以pydantic使用了一些很酷的新语言特性,但我为什么要实际去使用它呢?

  • 与您的 IDE/linter/brain 配合得很好

    无需学习新的模式定义微语言。如果你知道如何使用 Python 类型提示,你就会知道如何使用pydantic。数据结构只是您使用类型注释定义的类的实例,因此自动完成、linting、mypy、IDE(尤其是PyCharm)和您的直觉都应该与您的验证数据正常工作。

  • 两用

    pydantic 的 BaseSettings类允许在“验证此请求数据”上下文和“加载我的系统设置”上下文中使用pydantic 。主要区别在于系统设置可以从环境变量中读取,并且通常需要更复杂的对象,例如 DSN 和 Python 对象。

  • 快速地

    pydantic一直非常重视性能,大多数库都是用 cython 编译的,加速了约 50%,它通常与大多数类似库一样快或更快。

  • 验证复杂结构

    使用递归pydantic模型typing标准类型(例如ListTupleDict)和 验证器允许清晰、轻松地定义、验证和解析复杂的数据模式。

  • 可扩展

    pydantic允许定义自定义数据类型,或者您可以使用validator装饰器装饰的模型上的方法扩展验证。

  • 数据类集成

    以及BaseModelpydantic提供了一个dataclass装饰器,它创建(几乎)带有输入数据解析和验证的普通 Python 数据类。

3、 环境配置

安装这个库的方法非常简单:

pip install pydantic

安装配置的扩展功能:

pip install pydantic[email] # 邮箱验证支持
# or
pip install pydantic[dotenv] # dotenv文件支持
# or just
pip install pydantic[email, dotenv] # 同时安装

如果想要通过其他方式安装,可以参考官方文档

二、 Model

1、 模型属性

在pydantic中定义对象的主要方法是通过模型(模型只是继承自 的类BaseModel)。

您可以将模型视为类似于严格类型语言中的类型,或者视为 API 中单个端点的要求。

不受信任的数据可以传递给模型,并且在解析和验证之后,pydantic保证生成的模型实例的字段将符合模型上定义的字段类型。

属性 描述
dict() 返回模型字段和值的字典
json() 返回一个JSON字符串表示dict()
copy() 返回模型的副本,浅拷贝
parse_obj() 如果对象不是字典,则用于将任何对象加载到具有错误处理的模型中的实用程序
parse_raw() 用于加载多种格式字符串的实用程序
from_orm() 将数据从任意类加载到模型中
schema() 返回将模型表示为JSON Schema的字典
schema_json() 返回schema()的JSON字符串表示形式
construct() 无需运行验证即可创建模型的类方法
__fields_set__ 初始化模型实例时设置字段名称集
__fields__ 模型字段的字典
__config__ 模型的配置类

2、 基本使用

from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: float | None = None
class Bar(BaseModel):
apple = 'x'
banana = '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.dict())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [
{'apple': 'x1', 'banana': 'y'},
{'apple': 'x2', 'banana': 'y'},
],
}
"""

3、数据导入

3.1 orm

from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr
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):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: list[constr(max_length=255)]
class Config:
orm_mode = True
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode_3_9.CompanyOrm object at 0x7fb20cc17790>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']

3.2 pickle

import pickle
from datetime import datetime
from pydantic import BaseModel
pickle_data = 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
)
print(m)
#> id=123 signup_ts=datetime.datetime(2017, 7, 14, 0, 0) name='James'

3.3 json

from datetime import datetime
from pathlib import Path
from pydantic import BaseModel
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: datetime = None
path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)

4、 数据导出

print(user.dict()) # 转为字典
"""
{
'id': 123,
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'friends': [1, 2, 3],
'name': 'John Doe',
}
"""
print(user.json()) # 转为json
"""
{"id": 123, "signup_ts": "2019-06-01T12:22:00", "friends": [1, 2, 3], "name": "John Doe"}
"""
# 非常方便。它还支持将整个数据结构导出为 schema json,它能完整地描述整个对象的数据结构类型
print(user.schema_json(indent=2))
"""
{
"title": "User",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"signup_ts": {
"title": "Signup Ts",
"type": "string",
"format": "date-time"
},
"friends": {
"title": "Friends",
"default": [],
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"title": "Name",
"default": "John Doe",
"type": "string"
}
},
"required": [
"id"
]
}
"""

三、 验证器

1、 类内添加

能给它增加 validator 装饰器,增加你需要的校验逻辑

from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
username: 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
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
user = UserModel(
name='samuel colvin',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(
name='samuel',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn2',
)
except ValidationError as e:
print(e)
"""
2 validation errors for UserModel
name
must contain a space (type=value_error)
password2
passwords do not match (type=value_error)
"""

2、 重用验证器

有时,您会希望在多个字段/模型上使用相同的验证器(例如,规范化某些输入数据)。方法是编写一个单独的函数,然后从多个装饰器中调用它。显然,这需要大量重复和样板代码。为了避免这种情况,在v1.2allow_reuse中添加了该参数 (默认情况下)

from pydantic import BaseModel, validator
def normalize(name: str) -> str:
return ' '.join((word.capitalize()) for word in name.split(' '))
class Producer(BaseModel):
name: str
# validators
_normalize_name = validator('name', allow_reuse=True)(normalize)
class Consumer(BaseModel):
name: str
# validators
_normalize_name = validator('name', allow_reuse=True)(normalize)
jane_doe = Producer(name='JaNe DOE')
john_doe = Consumer(name='joHN dOe')
assert jane_doe.name == 'Jane Doe'
assert john_doe.name == 'John Doe'

3、 根验证器

from pydantic import BaseModel, ValidationError, root_validator
class UserModel(BaseModel):
username: str
password1: str
password2: str
@root_validator(pre=True)
def check_card_number_omitted(cls, values):
assert 'card_number' not in values, 'card_number should not be included'
return values
@root_validator
def check_passwords_match(cls, values):
pw1, pw2 = values.get('password1'), values.get('password2')
if pw1 is not None and pw2 is not None and pw1 != pw2:
raise ValueError('passwords do not match')
return values
print(UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn'))
#> username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(username='scolvin', password1='zxcvbn', password2='zxcvbn2')
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
__root__
passwords do not match (type=value_error)
"""
try:
UserModel(
username='scolvin',
password1='zxcvbn',
password2='zxcvbn',
card_number='1234',
)
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
__root__
card_number should not be included (type=assertion_error)
"""

与字段验证器一样,根验证器可以具有pre=True,在这种情况下,在字段验证发生之前调用它们(并提供原始输入数据),或者pre=False(默认),在这种情况下,在字段验证之后调用它们。

pre=True如果根验证器引发错误,则不会发生字段验证。与字段验证器一样,pre=False即使先前的验证器失败,也会默认调用“post”(即)根验证器;skip_on_failure=True可以通过将关键字参数设置为验证器来更改此行为。该values参数将是一个字典,其中包含通过字段验证的值和适用的字段默认值。

4、 验证装饰器

validate_arguments装饰器允许在调用函数之前使用函数的注释解析和验证传递给函数的参数。在引擎盖下,它使用相同的模型创建和初始化方法;它提供了一种非常简单的方法,可以用最少的样板对代码应用验证。

import os
from pathlib import Path
from typing import Pattern, Optional
from pydantic import validate_arguments, DirectoryPath
@validate_arguments
def find_file(path: DirectoryPath, regex: Pattern, max=None) -> Optional[Path]:
for i, f in enumerate(path.glob('**/*')):
if max and i > max:
return
if f.is_file() and regex.fullmatch(str(f.relative_to(path))):
return f
# note: this_dir is a string here
this_dir = os.path.dirname(__file__)
print(find_file(this_dir, '^validation.*'))
#> /home/runner/work/pydantic/pydantic/docs/examples/validation_decorator_async.
#> py
print(find_file(this_dir, '^foobar.*', max=3))
#> None

几点注意事项:

  • 尽管它们作为字符串传递,path并由装饰器分别regex转换为对象和正则表达式Path
  • max没有类型注释,所以会被装饰器认为是Any

这些是常用的方法,想要了解更详细的内容,可以去阅读官方文档:https://pydantic-docs.helpmanual.io/

posted @   Kenny_LZK  阅读(543)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示