【问题标题】:How to add type hint for dynamically created enums?如何为动态创建的枚举添加类型提示?
【发布时间】:2021-11-30 02:40:28
【问题描述】:

考虑到我正在创建一个Enum 类,如下所示,

from enum import Enum

key_value = {"DAY": "d", "WEEK": "w", "MONTH": "m"}
FooEnum = Enum("FooEnum", key_value)

如何使FooEnum 类表示正确的类型提示(以识别其属性,例如DAYWEEKMONTH)?

注意:在实际情况下,key_value 的大小会有点大 - 比如说 100 个条目。

【问题讨论】:

  • “如何静态输入提示”的答案通常是“你不能”

标签: python python-3.x enums


【解决方案1】:

确实 - 如果您的枚举是动态构建的,那么它就是“数据”而不是代码。 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))

【讨论】:

    【解决方案2】:

    如果值不改变并且不需要动态加载,您可以只使用 Enum 类。

    from enum import Enum
    
    class FooEnum(Enum):
        DAY:str = 'd'
        WEEK = 'w'
        MONTH = 'm'
    
    print(FooEnum.Day.value)
    

    【讨论】:

    • 如果我在那个特定的 dict 中有 100 个条目怎么办?
    • 我不明白为什么 100 个条目会成为问题。我会把它放在它自己的文件 FooEnum.py (或任何你命名的类)中,并将它包含在你需要的地方。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-10
    • 1970-01-01
    • 2016-09-02
    • 2017-08-19
    • 1970-01-01
    • 2011-04-27
    相关资源
    最近更新 更多