检查字段是否正在输入。可选

Rod*_*ino 8 python typing python-3.7

检查类中的字段是否正在打字的最佳方法是什么?可选?

示例代码:

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)))
Run Code Online (Sandbox Code Playgroud)

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

Gar*_*ett 9

作为参考,Python的3.8(第一释放2019年10月)中加入get_originget_args起到将typing模块。

文档中的示例:

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)
Run Code Online (Sandbox Code Playgroud)

这将允许:

def is_optional(field):
    return typing.get_origin(field) is Union and \
           type(None) in typing.get_args(field)
Run Code Online (Sandbox Code Playgroud)

对于较旧的 Python,这里有一些兼容性代码:

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)
Run Code Online (Sandbox Code Playgroud)

  • 如果使用新的 Union 表示法,即 str|None,这似乎不起作用。 (2认同)

Vic*_*uiz 7

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

现在,人们可以检查您输入字段批注的属性,以检查它是否像联盟[X,无]定义的:
你可以阅读它的属性__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

Run Code Online (Sandbox Code Playgroud)

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

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

如果所有条件都评估为真,则您有 typing.Optional[x]

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

x = Optional[int].__args__[0]
print(x) # class int
Run Code Online (Sandbox Code Playgroud)


Sim*_*imY 5

另一种方法(适用于 python 3.7 和 3.8)是中继 setUnion操作的工作原理:

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

逻辑是Optional类型不能是Optional呃。虽然您无法直接知道 a 是否type可为空/可选,但与可选和其他(确切地说)Optional[type]相同。typetypeUnion[type,None]

所以,在我们的例子中:

Union[SomeType,None] == Union[Union[SomeType,None]]
Run Code Online (Sandbox Code Playgroud)

(第一个相当于Optional[SomeType],第二个相当于Optional[Optional[SomeType]]

这可以非常轻松地检查Optional值:

from dataclasses import dataclass, fields
from typing import Optional


@dataclass()
class DC:
    x: Optional[str] = None
    y: str = "s"


def get_optional_fields(cls):
    fields_list = fields(cls)
    return [
        field.name 
        for field in fields_list if 
        field.type == Optional[field.type]
    ]



if __name__ == '__main__':
    print(get_optional_fields(DC())) # ['x']
Run Code Online (Sandbox Code Playgroud)


Abd*_*P M 2

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)))
Run Code Online (Sandbox Code Playgroud)