检查字段是否为 typing.Optional
Check if a field is typing.Optional
检查 class 中的字段是否为 typing.Optional 的最佳方法是什么?
示例代码:
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
字段。
我现在正在做的是使用基于字段名称的正则表达式来解决它,但我不喜欢这种方法。有更好的方法吗?
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)))
注: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'。如果不是,则注解不是 typing 模块定义的对象
__origin__
值等于 typing.Union
__args__
必须是包含 2 个项目的元组,其中第二个项目是 class NoneType (type(None)
)
如果所有条件都评估为真,则您有 typing.Optional[x]
您可能还需要知道注释中可选的 class 是什么:
x = Optional[int].__args__[0]
print(x) # class int
我编写了一个名为 typedload 的库,可用于执行此操作。
该库的主要目的是转换 to/from 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。
作为参考,Python 3.8(2019 年 10 月首次发布)向 typing
模块添加了 get_origin
和 get_args
函数。
示例来自 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)
对于较旧的 Python,这里是一些兼容性代码:
# Python >= 3.8
try:
from typing import Literal, get_args, get_origin
# Compatibility
except ImportError:
get_args = lambda t: getattr(t, '__args__', ()) \
if t is not Generic else Generic
get_origin = lambda t: getattr(t, '__origin__', None)
另一种方法(适用于 python 3.7 和 3.8)是根据 set Union
操作的工作原理:
union([x,y],[y])= union([x],[y]) = union(union([x],[y]),[x,y])
逻辑是 Optional
类型不能 Optional
er。虽然您无法直接知道 type
是否为 nullable/optional,但 Optional[type]
与 type
相同,只是 type
是可选的,而其他 (Union[type,None]
准确地说)否则。
因此,在我们的例子中:
Union[SomeType,None] == Union[Union[SomeType,None]]
(第一个等价于 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']
检查 class 中的字段是否为 typing.Optional 的最佳方法是什么?
示例代码:
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
字段。
我现在正在做的是使用基于字段名称的正则表达式来解决它,但我不喜欢这种方法。有更好的方法吗?
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)))
注: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'。如果不是,则注解不是 typing 模块定义的对象__origin__
值等于 typing.Union__args__
必须是包含 2 个项目的元组,其中第二个项目是 class NoneType (type(None)
)
如果所有条件都评估为真,则您有 typing.Optional[x]
您可能还需要知道注释中可选的 class 是什么:
x = Optional[int].__args__[0]
print(x) # class int
我编写了一个名为 typedload 的库,可用于执行此操作。
该库的主要目的是转换 to/from 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。
作为参考,Python 3.8(2019 年 10 月首次发布)向 typing
模块添加了 get_origin
和 get_args
函数。
示例来自 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)
对于较旧的 Python,这里是一些兼容性代码:
# Python >= 3.8
try:
from typing import Literal, get_args, get_origin
# Compatibility
except ImportError:
get_args = lambda t: getattr(t, '__args__', ()) \
if t is not Generic else Generic
get_origin = lambda t: getattr(t, '__origin__', None)
另一种方法(适用于 python 3.7 和 3.8)是根据 set Union
操作的工作原理:
union([x,y],[y])= union([x],[y]) = union(union([x],[y]),[x,y])
逻辑是 Optional
类型不能 Optional
er。虽然您无法直接知道 type
是否为 nullable/optional,但 Optional[type]
与 type
相同,只是 type
是可选的,而其他 (Union[type,None]
准确地说)否则。
因此,在我们的例子中:
Union[SomeType,None] == Union[Union[SomeType,None]]
(第一个等价于 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']