我正在使用Python 3.11,我需要检测一个可选的类属性是否为Enum类型(即Enum子类的类型)。
与typing.get_type_hints()
我可以得到类型提示作为字典,但如何检查如果字段的类型是可选的Enum(子类)?如果我能得到任何可选字段的类型就更好了,不管它是Optional[str]
,Optional[int]
,Optional[Class_X]
等。
from typing import Optional, get_type_hints
from enum import IntEnum, Enum
class TestEnum(IntEnum):
foo = 1
bar = 2
class Foo():
opt_enum : TestEnum | None = None
types = get_type_hints(Foo)['opt_enum']
这是(ipython)
In [4]: Optional[TestEnum] == types
Out[4]: True
这些都失败了
(是的,这是绝望的尝试)
In [6]: Optional[IntEnum] == types
Out[6]: False
和
In [11]: issubclass(Enum, types)
Out[11]: False
和
In [12]: issubclass(types, Enum)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [12], line 1
----> 1 issubclass(types, Enum)
TypeError: issubclass() arg 1 must be a class
和
In [13]: issubclass(types, Optional[Enum])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [13], line 1
----> 1 issubclass(types, Optional[Enum])
File /usr/lib/python3.10/typing.py:1264, in _UnionGenericAlias.__subclasscheck__(self, cls)
1262 def __subclasscheck__(self, cls):
1263 for arg in self.__args__:
-> 1264 if issubclass(cls, arg):
1265 return True
TypeError: issubclass() arg 1 must be a class
和
In [7]: IntEnum in types
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [7], line 1
----> 1 IntEnum in types
TypeError: argument of type 'types.UnionType' is not iterable
为什么我需要这个
我有几个情况下,我从csv文件导入数据,并从每行创建一个类的对象。csv.DictReader()
返回dict[str, str]
,我需要在尝试创建对象之前修复字段的类型。然而,一些对象字段是Optional[int]
,Optional[bool]
,Optional[EnumX]
或Optional[ClassX]
。我有几个这样的类多继承我的CSVImportable()
类/接口。我想在CSVImportable()
类中实现逻辑,而不是在每个子类中以字段感知的方式编写大致相同的代码。这个CSVImportable._field_type_updater()
应该:
- 至少正确更改基本类型和枚举的类型
- 优雅地跳过
Optional[ClassX]
字段
当然我也很感谢更好的设计:-)
当你处理一个参数化的类型(通用的或特殊的如typing.Optional
),你可以通过get_args
/get_origin
检查它。
这样做,您将看到T | S
的实现与typing.Union[T, S]
略有不同。前者为types.UnionType
,后者为typing.Union
。不幸的是,这意味着要覆盖这两个变量,我们需要两个不同的检查。
from types import UnionType
from typing import Union, get_origin
def is_union(t: object) -> bool:
origin = get_origin(t)
return origin is Union or origin is UnionType
使用typing.Optional
只是在引擎盖下使用typing.Union
,所以原点是相同的。下面是一个工作演示:
from enum import IntEnum
from types import UnionType
from typing import Optional, get_type_hints, get_args, get_origin, Union
class TestEnum(IntEnum):
foo = 1
bar = 2
class Foo:
opt_enum1: TestEnum | None = None
opt_enum2: Optional[TestEnum] = None
opt_enum3: TestEnum
opt4: str
def is_union(t: object) -> bool:
origin = get_origin(t)
return origin is Union or origin is UnionType
if __name__ == "__main__":
for name, type_ in get_type_hints(Foo).items():
if type_ is TestEnum or is_union(type_) and TestEnum in get_args(type_):
print(name, "accepts TestEnum")
输出:
opt_enum1接受TestEnumopt_enum2接受TestEnumopt_enum3接受TestEnum