确实 - 如果您的枚举是动态构建的,那么它就是“数据”而不是代码。 Python 中的静态类型检查处理的是代码。如果您在代码中编写“myvar.DAY”,并且 DAY 是由作为数据获取的字符串创建的枚举成员,Python 静态检查无法处理它:它们来自“不同的宇宙”。
最直接的做法是忽略为动态创建的枚举硬编码成员名称的行上的类型提示。
(有趣的事实,你可能会在尝试 thi 时遇到问题:mypy 将在你的 sn-p 中的 Enum 调用上出错:它只需要在对 Enum 的调用中键入 inline 的文字字典,并且会拒绝自省变量
断言它是一本字典。换句话说,FooEnum = Enum("FooEnum", key_value) 行使 mypy 错误,并带有“错误:Enum() 需要一个字符串、元组、列表或字典文字作为第二个参数”)
因此,难题已经解决:mypy 进行静态代码检查。以某种方式从动态数据中动态创建协议(typing.Protocol 的子类)并将您的 Enum 注册为它的实现也无法正常工作 - mypy 将无法静态检查协议的有效性。
除了如上所述用# type: ignore 标记所有消耗枚举的行之外,还有什么可能是使用枚举成员的静态子集创建协议 - 并在“.py”文件中至少硬编码在代码中显式使用的成员。
在“像往常一样的业务”中,mypy 会自动识别处理协议的类,而无需进行任何更改。这不是这里的情况:mypy 无论如何都不知道枚举里面有什么,所以我们需要在协议上显式调用register 方法来指示每个实现它的枚举。现在,还有另一个问题:由于 Enum 值和它们的类之间存在“混杂”关系,您不能只将 Enum 子类注册为实现协议并期望它工作 - mypy 将检查您的枚举实例,并且会“看到”它的类被传递,并且出错。
因此,不仅必须硬编码将在代码中实际使用的属性子集,还需要在调用将使用 Enum 的位置之前将枚举显式转换为协议类 (typing.cast)。
import typing as t
import json
from enum import Enum
key_value: dict = {"DAY": "d", "WEEK": "w", "MONTH": "m"}
DateEnum = Enum("DateEnum", key_value) # type: ignore
# ^ you can't escape from this "ignore" anyway.
TimeStampEnum = Enum("TimeStampEnum", json.load(open("file_with_time_units.json"))) # type: ignore
class MinimalTimeUnits(t.Protocol):
# we only care about using these in code:
DAY: str
MONTH: str
def blah(val: MinimalTimeUnits)-> None:
# Code uses just the enumeration members defined in the procotol:
x = f"{val.DAY} of {val.MONTH}"
some_condition = True # <- here so mypy won't error on missing variable on this example
def somecode()->None:
...
time_enum = DateEnum if some_condition else TimeStampEnum
blah(t.cast(MinimalTimeUnits, time_enum))