栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

验证python数据类中的详细类型

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

验证python数据类中的详细类型

而不是检查类型是否相等,应使用

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

# Python 3.7>>> import typing>>> typing.List.__origin__<class 'list'>>>> typing.List[int].__origin__<class 'list'>

Python
3.8通过

typing.get_origin()
自省功能引入了更好的支持:

# Python 3.8>>> import typing>>> typing.get_origin(typing.List)<class 'list'>>>> typing.get_origin(typing.List[int])<class 'list'>

值得注意的例外是

typing.Any
typing.Union
typing.ClassVar
…嗯,任何一个
typing._SpecialForm
没有定义
__origin__
。幸好:

>>> isinstance(typing.Union, typing._SpecialForm)True>>> isinstance(typing.Union[int, str], typing._SpecialForm)False>>> typing.get_origin(typing.Union[int, str])typing.Union

但是参数化类型定义了一个

__args__
属性,该属性将其参数存储为元组。Python
3.8引入了
typing.get_args()
检索它们的功能:

# 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'>)

因此,我们可以改进类型检查:

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

这不是完美的,因为它不会考虑

typing.ClassVar[typing.Union[int,str]]
typing.Optional[typing.List[int]]
为实例,但是它应该上手的东西。


接下来是应用此检查的方法。

除了使用之外

__post_init__
,我还可以使用装饰器路线:这可以用于具有类型提示的任何东西,不仅限于
dataclasses

import inspectimport typingfrom contextlib import suppressfrom functools import wrapsdef 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)

用法是:

@enforce_types@dataclasses.dataclassclass Point:    x: float    y: float@enforce_typesdef foo(bar: typing.Union[int, str]):    pass

Appart通过验证上一节中建议的某些类型提示,此方法仍存在一些缺点:

  • 使用字符串(类型提示
    class Foo: def __init__(self: 'Foo'): pass
    )不被考虑在内
    inspect.getfullargspec
    :您可能希望使用
    typing.get_type_hints
    inspect.signature
    替代;
  • 不验证不是适当类型的默认值:

    @enforce_type

    def foo(bar: int = None):
    pass

    foo()

没有筹集任何款项

TypeError
。如果您想对此加以考虑
inspect.Signature.bind
inspect.BoundArguments.apply_defaults
则可能需要结合使用(并因此迫使您定义
deffoo(bar: typing.Optional[int] = None)
);

  • 可变数量的参数无法通过验证,因为您必须定义类似的内容
    def foo(*args: typing.Sequence, **kwargs: typing.Mapping)
    ,如开头所述,我们只能验证容器,而不能验证包含的对象。

更新资料

在这个答案开始流行之后,一个受它启发很大的图书馆发布了,消除上述缺点的需求已成为现实。因此,我对该

typing
模块进行了更多介绍,并将在此处提出一些发现和一种新方法。

对于初学者来说,

typing
在寻找参数何时可选方面做得很好:

>>> def foo(a: int, b: str, c: typing.List[str] = None):...   pass... >>> typing.get_type_hints(foo){'a': <class 'int'>, 'b': <class 'str'>, 'c': typing.Union[typing.List[str], NoneType]}

这非常好,绝对是对的改进

inspect.getfullargspec
,因此最好使用它,因为它还可以正确地将字符串作为类型提示来处理。但是
typing.get_type_hints
会为其他类型的默认值提供援助:

>>> def foo(a: int, b: str, c: typing.List[str] = 3):...   pass... >>> typing.get_type_hints(foo){'a': <class 'int'>, 'b': <class 'str'>, 'c': typing.List[str]}

因此,即使您觉得这种情况非常麻烦,您仍可能需要进行更严格的检查。

接下来是用作或

typing
参数的提示的情况。由于这些中的始终是一个元组,因此可以递归地找到该元组中包含的提示的。结合以上检查,我们将需要过滤所有剩余的内容。
typing._SpecialForm``typing.Optional[typing.List[str]]``typing.Final[typing.Union[typing.Sequence,typing.Mapping]]``__args__``typing._SpecialForm``__origin__``typing._SpecialForm

拟议的改进:

import inspectimport typingfrom functools import wrapsdef _find_type_origin(type_hint):    if isinstance(type_hint, typing._SpecialForm):        # case of typing.Any, typing.ClassVar, typing.Final, typing.Literal,        # typing.NoReturn, typing.Optional, or typing.Union without parameters        yield typing.Any        return    actual_type = typing.get_origin(type_hint) or type_hint  # requires Python 3.8    if isinstance(actual_type, typing._SpecialForm):        # case of typing.Union[…] or typing.ClassVar[…] or …        for origins in map(_find_type_origin, typing.get_args(type_hint)): yield from origins    else:        yield actual_typedef _check_types(parameters, hints):    for name, value in parameters.items():        type_hint = hints.get(name, typing.Any)        actual_types = tuple(     origin     for origin in _find_type_origin(type_hint)     if origin is not typing.Any        )        if actual_types and not isinstance(value, actual_types): raise TypeError(         f"Expected type '{type_hint}' for argument '{name}'"         f" but received type '{type(value)}' instead" )def enforce_types(callable):    def decorate(func):        hints = typing.get_type_hints(func)        signature = inspect.signature(func)        @wraps(func)        def wrapper(*args, **kwargs): parameters = dict(zip(signature.parameters, args)) parameters.update(kwargs) _check_types(parameters, hints) return func(*args, **kwargs)        return wrapper    if inspect.isclass(callable):        callable.__init__ = decorate(callable.__init__)        return callable    return decorate(callable)def enforce_strict_types(callable):    def decorate(func):        hints = typing.get_type_hints(func)        signature = inspect.signature(func)        @wraps(func)        def wrapper(*args, **kwargs): bound = signature.bind(*args, **kwargs) bound.apply_defaults() parameters = dict(zip(signature.parameters, bound.args)) parameters.update(bound.kwargs) _check_types(parameters, hints) return func(*args, **kwargs)        return wrapper    if inspect.isclass(callable):        callable.__init__ = decorate(callable.__init__)        return callable    return decorate(callable)


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/650311.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号