Python3 类型提示及数据验证
基于Python typing的数据验证
Python3.5+ 之后,Python官方内置了一个数据类型标注的方式, 方便提高代码健壮性,捕获逻辑错误,后面代码示例解释。
简单的例子
from typing import List
# 比如创建一个函数
def greeting(name: str, hobby: List[str]) -> str:
"""
:param name: str类型参数
:param hobby: 只包含str的List类型参数
:return: str类型返回值
"""
return 'Hello ' + name + ",".join(hobby)
# 检测传入类型为对象实例
class HaHa(object):
def __init__(self, a=1):
self.a = a
def run(self):
print(self.a)
# 检测传入类型为对象 HaHa 的实例
def check_obj(n1: HaHa) -> None:
# 使用ide 这里run()方法会有友好的提示
n1.run()
h = HaHa(666)
# check_obj函数返回None,用一个参数接收,ide会提示语法逻辑问题
n = check_obj(h)
这样写有很多好处,最直观的就是ide的语法提示, 这个在 C, Java 里面是很基本语法,而且是强制要写返回值的;
稍微复杂点
from typing import List, Callable, Any, Union, Optional
# 类型别名 不重要
Vector = List[float]
# def scale(scalar: float, vector: List[float]) -> List[float]: # 同下一种写法
def scale(scalar: float, vector: Vector) -> Vector: # Vector 对上面的简写
"""
:param scalar: float 类型的参数
:param vector: Vector 类型, 即包含float类型的List
:return:
"""
return [scalar * num for num in vector]
print(scale(1.2, [1.2, 2.2]))
def foo(f1: int, f2: int) -> int:
return f1 * f2
# 回调函数类型 格式 Callable[[参数1类型, 参数2类型,..], 返回值类型] Any 任意类型 默认=1
def bar(b1: int, next_item: Callable[[int, int], int], b2: Any = 1) -> int:
return b1 + b2 + next_item(2, b1)
print(bar(2, foo))
def my_foo(n: Union[str, int]) -> str:
"""
Union 允许一个参数多种数据类型
:param n:
:return:
"""
return str(n)
def my_bar(n: Optional[str, int]) -> str:
"""
Optional 也是允许一个参数多种数据类型
Optional[x] 相当于 Union[x, None]的简写 官网描述: https://docs.python.org/zh-cn/3/library/typing.html#typing.Optional
:param n:
:return:
"""
return str(n)
# 使用pydantic Union的一些细节
# 比如 Union[str, int, UUID], 最好是把具体的类型写在最前面
# 改成这样最好 Union[UUID, int, str]
# 详情参考: https://pydantic-docs.helpmanual.io/usage/types/#unions
更多例子可以参考官网 https://docs.python.org/zh-cn/3/library/typing.html#module-typing
注意
当然上述类型 你不遵守也可以传值,调用,如下:
from typing import List, Union
def test_params(n1: List[float]) -> str:
# 这里 一般ide就会提示逻辑错误 join函数 期望是str类型的元素
return ",".join(n1)
# 如果n1 参数List包含多种类型应该这样写
# def test_params(n1: List[Union[str, int]]) -> str:
# 传入List[str]类型的参数 ide 就会提示逻辑错误
print(test_params(["abc", "12"]))
# 传入List[float] 会报错
print(test_params([1.3, 2.3]))
上面,示例代码,有几处明显错误,代码注释中有解释原因,借助ide提示会更直接,比如pycharm
有逻辑问题的代码,背景颜色就会有区别。
使用第三方库验证数据
- 需要安装依赖
pip install -U pydantic
- 官方文档 https://pydantic-docs.helpmanual.io/
pydantic 是一个类型注释的数据验证
import traceback
from datetime import datetime
from typing import List, Union, Optional
from pydantic import BaseSettings, AnyHttpUrl, IPvAnyAddress, EmailStr, BaseModel, ValidationError
class Config(BaseSettings):
""" 验证配置文件参数格式 """
MYSQL_USERNAME: str = ""
MYSQL_PASSWORD: str = ""
MYSQL_HOST: Union[AnyHttpUrl, IPvAnyAddress] = ""
MYSQL_DATABASE: str = ""
EMAIL_TEST_USER: EmailStr = ""
class User(BaseModel):
""" 验证参数类型 """
id: int
name = 'John Doe'
signup_ts: Optional[datetime] = None
friends: List[int] = []
# 正确的数据
correct_data = {
'id': '123',
'signup_ts': '2019-06-01 12:22',
'friends': [1, 2, '3']
}
# 错误的数据
error_data = {
"id": "aaa", # 规定id为int类型 错误
"name": "Floyd",
'signup_ts': '2019-06-01 12:22',
'friends': ["a", 2, '3'] # 规定List里面全是int类型 错误
}
def validation_data(check_data):
try:
user = User(**check_data)
except ValidationError as e: # 验证失败的时候
# traceback 打印错误栈
print(traceback.format_exc())
# 具体输出那里错误了
print(e.json())
return
print(user.id)
# > 123
print(repr(user.signup_ts))
print(user.signup_ts)
# > datetime.datetime(2019, 6, 1, 12, 22)
print(user.friends)
# > [1, 2, 3]
print(user.dict())
if __name__ == '__main__':
validation_data(correct_data)
validation_data(error_data)
# 第一个函数输出
"""
数据验证正确,正常输出
123
datetime.datetime(2019, 6, 1, 12, 22)
2019-06-01 12:22:00
[1, 2, 3]
{'id': 123, 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22), 'friends': [1, 2, 3], 'name': 'John Doe'}
"""
# 第二个函数输出
"""
# print(traceback.format_exc()) 打印的错误栈
Traceback (most recent call last):
File "/file_path/demo.py", line 49, in validation_data
user = User(**check_data)
File "pydantic/main.py", line 338, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 2 validation errors for User
id
value is not a valid integer (type=type_error.integer)
friends -> 0
value is not a valid integer (type=type_error.integer)
# e.json() 友好的输出错误提示
[
{
"loc": [
"id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"friends",
0
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
"""
上述的e.json()
可以很友好的输出参数错误提示。
pydantic
常见用途
- 固定参数配置验证格式,比如配置文件参数格式, 可参考FastAPI项目配置文件;
- 封装成装饰器,验证传入的参数,方便校验参数,捕获到异常,全局捕获ValidationError异常, 然后更友好的提示,参数异常;