您应该使用
isinstance
。但不能使用参数化的泛型类型(
typing.List[int]
)为此,必须使用“通用”版本(
typing.List
)因此,您可以检查容器类型,但不能检查包含的类型。参数化的泛型类型定义
__origin__
可以用于此目的的属性。
与python 3.6相反,在python3.7中,大多数类型提示都有一个有用的
_来源__
属性。比较:
# Python 3.6
>>> import typing
>>> typing.List.__origin__
>>> typing.List[int].__origin__
typing.List
和
# Python 3.7
>>> import typing
>>> typing.List.__origin__
<class 'list'>
>>> typing.List[int].__origin__
<class 'list'>
值得注意的例外是
typing.Any
,
typing.Union
和
typing.ClassVar
好吧,只要是
typing._SpecialForm
不定义
_来源__
. 幸运的是:
>>> isinstance(typing.Union, typing._SpecialForm)
True
>>> isinstance(typing.Union[int, str], typing._SpecialForm)
False
>>> typing.Union[int, str].__origin__
typing.Union
但是参数化类型定义了
__args__
将参数存储为元组的属性:
>>> typing.Union[int, str].__args__
(<class 'int'>, <class 'str'>)
因此,我们可以稍微改进类型检查:
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:
actual_type = 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
这不完美,因为它不能解释
typing.ClassVar[typing.Union[int, str]]
或
typing.Optional[typing.List[int]]
例如,但它应该能让事情开始。
接下来是应用此检查的方法。
而不是使用
__post_init__
,我将转到decorator路径:这不仅可以用于具有类型提示的任何内容
dataclasses
:
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:
actual_type = 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)
用法为:
@enforce_types
@dataclasses.dataclass
class Point:
x: float
y: float
@enforce_types
def foo(bar: typing.Union[int, str]):
pass
通过验证前一节中建议的一些类型提示,这种方法仍然有一些缺点:
多亏了
@Aran-Fey
这帮助我改进了这个答案。