检查类中的字段是否正在键入的最好方法是什么?
示例代码:
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
字段。
我目前正在解决该问题的方法是使用基于正则表达式的字段名称,但我不喜欢这种方法。有更好的方法吗?
答案 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
您需要执行以下步骤来定义您的检查器:
__module__
,__args__
和__origin__
__module__
必须设置为“ typing”。如果不是,则注释不是打字模块定义的对象__origin__
的值等于输入值。Union__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_args
和typing
函数。
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__', ())