验证装饰器
The validate_call()
装饰器允许在调用函数之前,使用函数的注解来解析和验证传递给函数的参数。
虽然在底层,这使用了相同的模型创建和初始化方法(详见 验证器),但它提供了一种极其简便的方式,以最少的样板代码将验证应用于您的代码。
使用示例
from pydantic import ValidationError, validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat('x', '4', separator=b' ')
print(b)
#> b'x x x x'
try:
c = repeat('hello', 'wrong')
except ValidationError as exc:
print(exc)
"""
1 validation error for repeat
1
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='wrong', input_type=str]
"""
参数类型¶
参数类型从函数上的类型注解推断,如果未注解,则推断为 Any
。 类型 中列出的所有类型都可以被验证,包括 Pydantic 模型和 自定义类型。 与 Pydantic 的其余部分一样,默认情况下,类型在传递给实际函数之前会被装饰器强制转换
from datetime import date
from pydantic import validate_call
@validate_call
def greater_than(d1: date, d2: date, *, include_equal=False) -> date: # (1)!
if include_equal:
return d1 >= d2
else:
return d1 > d2
d1 = '2000-01-01' # (2)!
d2 = date(2001, 1, 1)
greater_than(d1, d2, include_equal=True)
像这样的类型强制转换可能非常有用,但也可能令人困惑或不希望出现(请参阅 模型数据转换)。 可以通过使用 自定义配置 来启用 严格模式。
验证返回值
默认情况下,函数的返回值不进行验证。 要这样做,可以将装饰器的 validate_return
参数设置为 True
。
函数签名¶
The validate_call()
装饰器旨在与使用所有可能的 参数配置 以及这些配置的所有可能组合的函数一起使用
- 带有或不带有默认值的位置或关键字参数。
- 仅关键字参数:在
*,
之后的参数。 - 仅位置参数:在
, /
之前的参数。 - 通过
*
定义的可变位置参数(通常是*args
)。 - 通过
**
定义的可变关键字参数(通常是**kwargs
)。
示例
from pydantic import validate_call
@validate_call
def pos_or_kw(a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(pos_or_kw(1, b=3))
#> a=1 b=3
@validate_call
def kw_only(*, a: int, b: int = 2) -> str:
return f'a={a} b={b}'
print(kw_only(a=1))
#> a=1 b=2
print(kw_only(a=1, b=3))
#> a=1 b=3
@validate_call
def pos_only(a: int, b: int = 2, /) -> str:
return f'a={a} b={b}'
print(pos_only(1))
#> a=1 b=2
@validate_call
def var_args(*args: int) -> str:
return str(args)
print(var_args(1))
#> (1,)
print(var_args(1, 2, 3))
#> (1, 2, 3)
@validate_call
def var_kwargs(**kwargs: int) -> str:
return str(kwargs)
print(var_kwargs(a=1))
#> {'a': 1}
print(var_kwargs(a=1, b=2))
#> {'a': 1, 'b': 2}
@validate_call
def armageddon(
a: int,
/,
b: int,
*c: int,
d: int,
e: int = None,
**f: int,
) -> str:
return f'a={a} b={b} c={c} d={d} e={e} f={f}'
print(armageddon(1, 2, d=3))
#> a=1 b=2 c=() d=3 e=None f={}
print(armageddon(1, 2, 3, 4, 5, 6, d=8, e=9, f=10, spam=11))
#> a=1 b=2 c=(3, 4, 5, 6) d=8 e=9 f={'f': 10, 'spam': 11}
用于关键字参数的 Unpack
Unpack
和类型化的字典可以用来注解函数的可变关键字参数
from typing_extensions import TypedDict, Unpack
from pydantic import validate_call
class Point(TypedDict):
x: int
y: int
@validate_call
def add_coords(**kwargs: Unpack[Point]) -> int:
return kwargs['x'] + kwargs['y']
add_coords(x=1, y=2)
使用 Field()
函数描述函数参数¶
Field()
函数 也可以与装饰器一起使用,以提供有关字段和验证的额外信息。 如果您不使用 default
或 default_factory
参数,建议使用 注解模式(以便类型检查器推断参数为必需)。 否则,Field()
函数可以用作默认值(同样,为了欺骗类型检查器,使其认为为参数提供了默认值)。
from typing import Annotated
from pydantic import Field, ValidationError, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10)]):
return num
try:
how_many(1)
except ValidationError as e:
print(e)
"""
1 validation error for how_many
0
Input should be greater than 10 [type=greater_than, input_value=1, input_type=int]
"""
@validate_call
def return_value(value: str = Field(default='default value')):
return value
print(return_value())
#> default value
别名 可以像往常一样与装饰器一起使用
from typing import Annotated
from pydantic import Field, validate_call
@validate_call
def how_many(num: Annotated[int, Field(gt=10, alias='number')]):
return num
how_many(number=42)
访问原始函数¶
仍然可以使用 raw_function
属性访问被装饰的原始函数。 如果在某些情况下您信任您的输入参数,并希望以最有效的方式调用该函数,这将非常有用(请参阅下面的 性能说明)
from pydantic import validate_call
@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
b = s.encode()
return separator.join(b for _ in range(count))
a = repeat('hello', 3)
print(a)
#> b'hellohellohello'
b = repeat.raw_function('good bye', 2, separator=b', ')
print(b)
#> b'good bye, good bye'
异步函数¶
validate_call()
也可以用于异步函数
class Connection:
async def execute(self, sql, *args):
return '[email protected]'
conn = Connection()
# ignore-above
import asyncio
from pydantic import PositiveInt, ValidationError, validate_call
@validate_call
async def get_user_email(user_id: PositiveInt):
# `conn` is some fictional connection to a database
email = await conn.execute('select email from users where id=$1', user_id)
if email is None:
raise RuntimeError('user not found')
else:
return email
async def main():
email = await get_user_email(123)
print(email)
#> [email protected]
try:
await get_user_email(-4)
except ValidationError as exc:
print(exc.errors())
"""
[
{
'type': 'greater_than',
'loc': (0,),
'msg': 'Input should be greater than 0',
'input': -4,
'ctx': {'gt': 0},
'url': 'https://errors.pydantic.dev/2/v/greater_than',
}
]
"""
asyncio.run(main())
# requires: `conn.execute()` that will return `'[email protected]'`
与类型检查器的兼容性¶
由于 validate_call()
装饰器保留了被装饰函数的签名,因此它应该与类型检查器(例如 mypy 和 pyright)兼容。 但是,由于 Python 类型系统目前的限制,raw_function
或其他属性将无法被识别,您需要通过(通常使用 # type: ignore
注释)来抑制错误。
自定义配置¶
与 Pydantic 模型类似,装饰器的 config
参数可用于指定自定义配置
from pydantic import ConfigDict, ValidationError, validate_call
class Foobar:
def __init__(self, v: str):
self.v = v
def __add__(self, other: 'Foobar') -> str:
return f'{self} + {other}'
def __str__(self) -> str:
return f'Foobar({self.v})'
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
return a + b
c = add_foobars(Foobar('a'), Foobar('b'))
print(c)
#> Foobar(a) + Foobar(b)
try:
add_foobars(1, 2)
except ValidationError as e:
print(e)
"""
2 validation errors for add_foobars
0
Input should be an instance of Foobar [type=is_instance_of, input_value=1, input_type=int]
1
Input should be an instance of Foobar [type=is_instance_of, input_value=2, input_type=int]
"""
扩展 — 在调用函数之前验证参数¶
在某些情况下,将函数参数的验证与函数调用本身分离可能很有用。 当特定函数成本高昂/耗时时,这可能很有用。
这是一个您可以用于该模式的变通方法示例
from pydantic import validate_call
@validate_call
def validate_foo(a: int, b: int):
def foo():
return a + b
return foo
foo = validate_foo(a=1, b=2)
print(foo())
#> 3
局限性¶
验证异常¶
目前,在验证失败时,会引发标准的 Pydantic ValidationError
异常(有关详细信息,请参阅 模型错误处理)。 这对于缺少必需参数的情况也是如此,在这种情况下,Python 通常会引发 TypeError
。
性能¶
我们已尽力使 Pydantic 尽可能高性能。 虽然对装饰函数的检查仅执行一次,但在调用函数时,与使用原始函数相比,仍然会存在性能影响。
在许多情况下,这几乎没有或没有明显的影响。 但是,请注意,validate_call()
不是强类型语言中函数定义的等价物或替代品,而且永远不会是。