检查字段是否在键入。

时间:2019-07-01 09:08:55

标签: python python-3.7 typing

检查类中的字段是否正在键入的最好方法是什么?

示例代码:

from typing import Optional
import re
from dataclasses import dataclass, fields

@dataclass(frozen=True)
class TestClass:
    required_field_1: str
    required_field_2: int
    optional_field: Optional[str]

def get_all_optional_fields(fields) -> list:
    return [field.name for field in fields if __is_optional_field(field)]

def __is_optional_field(field) -> bool:
    regex = '^typing.Union\[.*, NoneType\]$'
    return re.match(regex, str(field.type)) is not None

print(get_all_optional_fields(fields(TestClass)))

fields来自dataclasses,我想列出所有Optional字段。 我目前正在解决该问题的方法是使用基于正则表达式的字段名称,但我不喜欢这种方法。有更好的方法吗?

5 个答案:

答案 0 :(得分:2)

注意:typing.Optional[x]typing.Union[x, None]的别名

现在,您可以检查输入字段批注的属性,以检查其定义是否像Union [x,None]:
您可以阅读其属性__module____args____origin__

from typing import *

def print_meta_info(x):
      print(x.__module__, x.__args__, x.__origin__)

x = Optional[int]
print_meta_info(x) # 'typing', (class Int,), typing.Union

x = Union[int, float]
print_meta_info(x) # 'typing', (class int, class float), typing.Union

x = Iterable[str]
print_meta_info(x) # 'typing', (class int,), typing.Iterable

您需要执行以下步骤来定义您的检查器:

  1. 确保注释具有键__module____args____origin__
  2. __module__必须设置为“ typing”。如果不是,则注释不是打字模块定义的对象
  3. __origin__的值等于输入值。Union
  4. __args__必须是包含2个项目的元组,其中第二个是NoneType(type(None))类

如果所有条件都被评估为true,则可以输入。Optional[x]

您可能还需要知道注释中的可选类是什么:

x = Optional[int].__args__[0]
print(x) # class int

答案 1 :(得分:2)

另一种方法(在python 3.7和3.8上都适用)是中继设置Union的工作原理:

union([x,y],[y])= union([x],[y]) = union(union([x],[y]),[x,y])

逻辑是Optional类型不能为Optional er。虽然您无法直接知道type是否可为空/可选,但Optional[type]type相同,只是type是可选的,其他({{1} },否则。

因此,在我们的例子中:

Union[type,None]

(第一个等同于Union[SomeType,None] == Union[Union[SomeType,None]] ,第二个等同于Optional[SomeType]

这可以非常容易地检查Optional[Optional[SomeType]]值:

Optional

答案 2 :(得分:1)

Optional[X]等效于Union[X, None]。这样就可以了,

import re
from typing import Optional

from dataclasses import dataclass, fields


@dataclass(frozen=True)
class TestClass:
    required_field_1: str
    required_field_2: int
    optional_field: Optional[str]


def get_optional_fields(klass):
    class_fields = fields(klass)
    for field in class_fields:
        if (
            hasattr(field.type, "__args__")
            and len(field.type.__args__) == 2
            and field.type.__args__[-1] is type(None)
        ):
            # Check if exactly two arguments exists and one of them are None type
            yield field.name


print(list(get_optional_fields(TestClass)))

答案 3 :(得分:0)

我写了一个叫做typedload的库。

该库的主要目的是在json和namedtuple / dataclass / attrs之间进行转换,但是由于需要进行这些检查,因此它公开了这些函数。

请注意,不同版本的python会更改内部键入API的工作方式,因此检查不适用于每个python版本。

我的图书馆在内部对其进行处理,将详细信息隐藏给用户。

使用它,代码就是这样

from typing import *
a = Optional[int]

from typedload import typechecks
typechecks.is_union(a) and type(None) in typechecks.uniontypes(a)

https://github.com/ltworf/typedload

当然,如果您不需要支持多个python版本,则可能不希望仅为此而依赖库,但是将来的发行版可能会破坏检查。他们甚至在次要发行版之间也更改了API。

答案 4 :(得分:0)

作为参考,Python 3.8(于2019年10月首次发布)在get_origin模块中添加了get_argstyping函数。

docs中的示例:

assert get_origin(Dict[str, int]) is dict
assert get_args(Dict[int, str]) == (int, str)

assert get_origin(Union[int, str]) is Union
assert get_args(Union[int, str]) == (int, str)

这将允许:

def is_optional(field):
    return typing.get_origin(field) is Union and type(None) in typing.get_args(field)

或具有后备选项:

def is_optional(field):
    if (sys.version_info.major, sys.version_info.minor) >= (3, 8):
        return typing.get_origin(field.type) is typing.Union and \
            type(None) in typing.get_args(field.type)
    return getattr(field.type, '__origin__', None) is typing.Union and \
        type(None) in getattr(field.type, '__args__', ())