검증 데코레이터
??? api "API 문서" [pydantic.validate_call_decorator.validate_call
][pydantic.validate_call_ decorator.validate_call]
@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의 나머지 부분과 마찬가지로 유형은 실제 함수에 전달되기 전에 데코레이터에 의해 강제될 수 있습니다.
# TODO replace find_file with something that isn't affected the filesystem
import os
from pathlib import Path
from typing import Optional, Pattern
from pydantic import DirectoryPath, validate_call
@validate_call
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.*'))
print(find_file(this_dir, '^foobar.*', max=3))
몇 가지 참고사항:
- 문자열로 전달되지만
path
및regex
데코레이터에 의해 각각Path
객체 및 regex로 변환됩니다. max
에는 유형 주석이 없으므로 데코레이터에서는Any
로 간주됩니다.
이와 같은 유형 강제는 매우 도움이 될 수 있지만 혼란스럽거나 바람직하지 않을 수도 있습니다. 이와 관련하여 @validate_call
의 제한 사항에 대한 논의는 강제 및 엄격성을 참조하세요.
함수 서명¶
@validate_call
데코레이터는 가능한 모든 매개변수 구성과 이들의 가능한 모든 조합을 사용하여 함수와 함께 작동하도록 설계되었습니다.
- 기본값이 있거나 없는 위치 또는 키워드 인수입니다.
*
통해 정의된 가변 위치 인수(종종*args
).**
(종종**kwargs
)를 통해 정의된 가변 키워드 인수입니다.- 키워드 전용 인수:
*,
. - 위치 전용 인수:
, /
앞의 인수(Python 3.8의 새로운 기능).
위의 모든 매개변수 유형을 보여주기 위해:
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))
#> a=1 b=2
print(pos_or_kw(a=1))
#> a=1 b=2
print(pos_or_kw(1, 3))
#> a=1 b=3
print(pos_or_kw(a=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: # python 3.8 only
return f'a={a} b={b}'
print(pos_only(1))
#> a=1 b=2
print(pos_only(1, 2))
#> 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))
#> (1, 2)
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,
/, # python 3.8 only
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}
필드를 사용하여 함수 인수 설명¶
필드와 유효성 검사에 대한 추가 정보를 제공하기 위해 필드를 @validate_call
과 함께 사용할 수도 있습니다. 일반적으로 default_factory
지정되지 않는 한 Annotated 가 있는 유형 힌트에 사용해야 하며, 이 경우 필드의 기본값으로 사용해야 합니다.
from datetime import datetime
from typing_extensions 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 when(dt: datetime = Field(default_factory=datetime.now)):
return dt
print(type(when()))
#> <class 'datetime.datetime'>
alias
정상적으로 데코레이터와 함께 사용할 수 있습니다.
from typing_extensions 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)
mypy와 함께 사용하기¶
validate_call
데코레이터는 데코레이션하는 함수와 동일한 시그니처를 가진 함수를 반환하도록 정의되어 있으므로 mypy 와 함께 "즉시" 작동해야 합니다. 유일한 제한은 장식자가 반환한 함수가 장식되는 함수와 동일하다고 생각하도록 mypy를 속이기 때문입니다. 원시 함수 또는 기타 속성에 액세스하려면 type: ignore
필요합니다.
원시 함수¶
장식된 원시 함수에 액세스할 수 있습니다. 이는 일부 시나리오에서 입력 인수를 신뢰하고 가장 성능이 좋은 방식으로 함수를 호출하려는 경우에 유용합니다(아래 성능에 대한 참고 사항 참조).
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 'testing@example.com'
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)
#> testing@example.com
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 `'testing@example.com'`
맞춤 구성¶
@validate_call
뒤에 있는 모델은 일반 모델에서 ConfigDict
하위 클래스를 설정하는 것과 동일한 config
설정을 사용하여 사용자 정의할 수 있습니다.
구성은 데코레이터에 대한 config
키워드 인수를 사용하여 설정되며, 구성 클래스이거나 나중에 클래스로 변환되는 속성의 사전일 수 있습니다.
from pydantic import 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=dict(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
가 발생합니다. 자세한 내용은 모델 오류 처리를 참조하세요.
이는 str()
메서드가 발생한 오류에 대한 유용한 세부 정보를 제공하고 .errors()
및 .json()
과 같은 메서드가 오류를 최종 사용자에게 노출할 때 유용할 수 있기 때문에 유용합니다. 그러나 ValidationError
TypeError
아닌 ValueError
에서 상속되는데, 이는 Python이 유효하지 않거나 누락된 인수에 대해 TypeError
발생시키므로 예상치 못한 결과일 수 있습니다. 이 문제는 나중에 사용자 지정 오류를 허용하거나 기본적으로 다른 예외를 발생시키거나 두 가지 모두를 통해 해결될 수 있습니다.
강압과 엄격함¶
Pydantic은 현재 유형이 잘못된 경우 오류를 발생시키는 대신 유형을 강제하는 편에 기울고 있습니다. 모델 데이터 변환을 참조하세요. @validate_call
도 다르지 않습니다.
성능¶
우리는 Pydantic을 가능한 한 성능 좋게 만들기 위해 많은 노력을 기울였으며 인수 검사와 모델 생성은 함수가 정의될 때 한 번만 수행됩니다. 그러나 원시 함수를 호출하는 것과 비교하여 @validate_call
데코레이터를 사용하면 성능에 여전히 영향이 있습니다. .
많은 상황에서 이는 눈에 띄는 효과가 거의 또는 전혀 없습니다. 그러나 @validate_call
강력한 유형의 언어에서 함수 정의와 동등하거나 대안이 아니라는 점에 유의하십시오. 결코 그렇지 않을 것입니다.
반환 값¶
함수의 반환 값은 반환 유형 주석에 대해 선택적으로 유효성을 검사할 수 있습니다.
本文总阅读量次