Arn*_*rne 18 python typing python-dataclasses
Python 3.7即将到来,我想测试一些奇特的新dataclass+打字功能.使用本机类型和typing模块中的类型,可以很容易地获得正确工作的提示:
>>> import dataclasses
>>> import typing as ty
>>>
... @dataclasses.dataclass
... class Structure:
... a_str: str
... a_str_list: ty.List[str]
...
>>> my_struct = Structure(a_str='test', a_str_list=['t', 'e', 's', 't'])
>>> my_struct.a_str_list[0]. # IDE suggests all the string methods :)
Run Code Online (Sandbox Code Playgroud)
但是我想要尝试的另一件事是在运行时强制类型提示作为条件,即不应该dataclass存在具有不正确类型的类型.它可以很好地实现__post_init__:
>>> @dataclasses.dataclass
... class Structure:
... a_str: str
... a_str_list: ty.List[str]
...
... def validate(self):
... ret = True
... for field_name, field_def in self.__dataclass_fields__.items():
... actual_type = type(getattr(self, field_name))
... if actual_type != field_def.type:
... print(f"\t{field_name}: '{actual_type}' instead of '{field_def.type}'")
... ret = False
... return ret
...
... def __post_init__(self):
... if not self.validate():
... raise ValueError('Wrong types')
Run Code Online (Sandbox Code Playgroud)
这种validate函数适用于本机类型和自定义类,但不适用于typing模块指定的类型:
>>> my_struct = Structure(a_str='test', a_str_list=['t', 'e', 's', 't'])
Traceback (most recent call last):
a_str_list: '<class 'list'>' instead of 'typing.List[str]'
ValueError: Wrong types
Run Code Online (Sandbox Code Playgroud)
是否有更好的方法来验证带有类型的无类型列表typing?优选地,一个不包括检查类型的所有元素的任何list,dict,tuple,或set这是一个dataclass"属性.
409*_*ict 28
您应该使用而不是检查类型相等isinstance.但是您不能使用参数化泛型类型(typing.List[int])来执行此操作,您必须使用"通用"版本(typing.List).因此,您将能够检查容器类型,但不能检查包含的类型.参数化泛型类型定义了__origin__可用于该属性的属性.
与Python 3.6相反,在Python 3.7中,大多数类型提示都有一个有用的__origin__属性.相比:
# Python 3.6
>>> import typing
>>> typing.List.__origin__
>>> typing.List[int].__origin__
typing.List
Run Code Online (Sandbox Code Playgroud)
和
# Python 3.7
>>> import typing
>>> typing.List.__origin__
<class 'list'>
>>> typing.List[int].__origin__
<class 'list'>
Run Code Online (Sandbox Code Playgroud)
值得注意的例外是typing.get_origin(),typing.Any和typing.Union...嗯,任何一个typing.ClassVar没有定义typing._SpecialForm.幸好:
# Python 3.8
>>> import typing
>>> typing.get_origin(typing.List)
<class 'list'>
>>> typing.get_origin(typing.List[int])
<class 'list'>
Run Code Online (Sandbox Code Playgroud)
但是参数化类型定义了一个__origin__将其参数存储为元组的属性:
>>> isinstance(typing.Union, typing._SpecialForm)
True
>>> isinstance(typing.Union[int, str], typing._SpecialForm)
False
>>> typing.get_origin(typing.Union[int, str])
typing.Union
Run Code Online (Sandbox Code Playgroud)
所以我们可以改进一下类型检查:
# Python 3.7
>>> typing.Union[int, str].__args__
(<class 'int'>, <class 'str'>)
# Python 3.8
>>> typing.get_args(typing.Union[int, str])
(<class 'int'>, <class 'str'>)
Run Code Online (Sandbox Code Playgroud)
这并不完美,因为它不会考虑__args__或者typing.get_args()例如,但它应该开始.
接下来是应用此检查的方法.
而不是使用typing.ClassVar[typing.Union[int, str]],我会去装饰路线:这可以用于任何类型提示,不仅typing.Optional[typing.List[int]]:
for field_name, field_def in self.__dataclass_fields__.items():
if isinstance(field_def.type, typing._SpecialForm):
# No check for typing.Any, typing.Union, typing.ClassVar (without parameters)
continue
try:
actual_type = field_def.type.__origin__
except AttributeError:
# In case of non-typing types (such as <class 'int'>, for instance)
actual_type = field_def.type
# In Python 3.8 one would replace the try/except with
# actual_type = typing.get_origin(field_def.type) or field_def.type
if isinstance(actual_type, typing._SpecialForm):
# case of typing.Union[…] or typing.ClassVar[…]
actual_type = field_def.type.__args__
actual_value = getattr(self, field_name)
if not isinstance(actual_value, actual_type):
print(f"\t{field_name}: '{type(actual_value)}' instead of '{field_def.type}'")
ret = False
Run Code Online (Sandbox Code Playgroud)
用法是:
import inspect
import typing
from contextlib import suppress
from functools import wraps
def enforce_types(callable):
spec = inspect.getfullargspec(callable)
def check_types(*args, **kwargs):
parameters = dict(zip(spec.args, args))
parameters.update(kwargs)
for name, value in parameters.items():
with suppress(KeyError): # Assume un-annotated parameters can be any type
type_hint = spec.annotations[name]
if isinstance(type_hint, typing._SpecialForm):
# No check for typing.Any, typing.Union, typing.ClassVar (without parameters)
continue
try:
actual_type = type_hint.__origin__
except AttributeError:
# In case of non-typing types (such as <class 'int'>, for instance)
actual_type = type_hint
# In Python 3.8 one would replace the try/except with
# actual_type = typing.get_origin(type_hint) or type_hint
if isinstance(actual_type, typing._SpecialForm):
# case of typing.Union[…] or typing.ClassVar[…]
actual_type = type_hint.__args__
if not isinstance(value, actual_type):
raise TypeError('Unexpected type for \'{}\' (expected {} but found {})'.format(name, type_hint, type(value)))
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
check_types(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
if inspect.isclass(callable):
callable.__init__ = decorate(callable.__init__)
return callable
return decorate(callable)
Run Code Online (Sandbox Code Playgroud)
通过验证上一节中建议的某些类型提示,这种方法仍有一些缺点:
__post_init__)的类型提示不会被考虑dataclasses:您可能想要使用class Foo: def __init__(self: 'Foo'): pass而inspect.getfullargspec不是;未验证默认值不是合适的类型:
@enforce_types
@dataclasses.dataclass
class Point:
x: float
y: float
@enforce_types
def foo(bar: typing.Union[int, str]):
pass
Run Code Online (Sandbox Code Playgroud)
没有提出任何typing.get_type_hints.您可能需要使用inspect.signature在同一起选择TypeError,如果你想以考虑(并因此迫使你定义inspect.Signature.bind);
inspect.BoundArguments.apply_defaults,如开头所述,我们只能验证容器而不是包含对象.感谢@ Aran-Fey,帮助我改进了这个答案.
刚发现这个问题。
pydantic可以直接对数据类进行全类型验证。(入场费:我修造了dan
只需使用pydantic的装饰器版本,结果数据类将完全是原始的。
from datetime import datetime
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
signup_ts: datetime = None
print(User(id=42, signup_ts='2032-06-21T12:00'))
"""
User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""
User(id='not int', signup_ts='2032-06-21T12:00')
Run Code Online (Sandbox Code Playgroud)
最后一行将给出:
...
pydantic.error_wrappers.ValidationError: 1 validation error
id
value is not a valid integer (type=type_error.integer)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6707 次 |
| 最近记录: |