用特定类型的值表示 Enum 的正确方法

Correct way to represent an Enum with values of specific types

我正在尝试表示一个枚举,其中每个键的值都必须具有特定类型。例如,我定义了这个:

from enum import Enum, auto
from typing import Any

class MatchType(Enum):
    NATIVE = auto()
    DICTIONARY = auto()
    LIST = auto()

class MatchResult:
    type: MatchType
    value: Any

    def __init__(self, type: MatchType, value: Any):
        self.type = type
        self.value = value

现在如何将这些类型关联到相应的值类型?我的意思是,如果函数 returns a MatchResult with type = MatchType.NATIVE 我想 Mypy 检查我是否使用 float | int | string | bool 作为值:

def fn_returs_primitive() -> MathResult:
    ...
    return MathResult(MatchType.NATIVE, [1, 2, 3])  # This CAN NOT happen as NATIVE should be int, float, string or boolean, NOT a list

如何确保在 Python?

例如,在 Rust 中,您可以定义一个枚举,其中每个类型都有参数:

use std::collections::HashMap;

enum MatchType<T> {
    NATIVE(u32),
    DICTIONATY(HashMap<String, T>),
    LIST(Vec<T>)
}

Python 中是否存在类似的东西?任何形式的帮助将不胜感激

Python 有一个未标记的联合类型,称为 Union。这种类型被认为是未标记的,因为没有信息存储选择了枚举的哪个变体。对于您的用例,这是一个未标记的实现:

from typing import TypeVar, Union

T = TypeVar("T")
MatchType = Union[int, dict[str, T], list[T]]

def get() -> MatchType:
    return [1, 2, 3]

def match_on(match_type: MatchType):
    if isinstance(match_type, int):
        print("An int.")
    elif isinstance(match_type, dict):
        print("A dict.")
    elif isinstance(match_type, list):
        print("A list.")

但是请注意,我们必须在匹配期间遍历所有可能的 MatchType。这是因为没有标签与我们可以索引 map / table 的未标记联合的变体一起存储。进行恒定时间匹配的幼稚尝试可能如下所示:

def match_on(match_type: MatchType):
    {
        int: lambda: print("An int."),
        dict: lambda: print("A dictionary."),
        list: lambda: print("A list.")
    }[type(match_type)]()

但是给定 int 的子类,这将抛出 IndexError 因为类型不是严格的 int.

要启用像 Rust 编译器可能发出的用于匹配标记联合的恒定时间匹配,您必须像这样模仿标记联合:

from dataclasses import dataclass
from typing import TypeVar, final, Generic, Union

T = TypeVar("T")

@final
@dataclass
class MatchNative:
    value: int

@final
@dataclass
class MatchDictionary(Generic[T]):
    value: dict[str, T]

# Avoid collision with built in type `List` by prepending `Match`.
@final
@dataclass
class MatchList(Generic[T]):
    value: list[T]

MatchType = Union[MatchNative, MatchDictionary[T], MatchList[T]]

def get():
    return MatchList([1, 2, 3])

def match_on(match_type: MatchType):
    {
        MatchNative: lambda: print("An int."),
        MatchDictionary: lambda: print("A dictionary."),
        MatchList: lambda: print("A list.")
    }[type(match_type)]()

不需要 @dataclass 注释,它只是为我随后在 get 函数中使用的标签创建一个 __init__

在这里,我们创建了三个 类,其中包含每种类型的相关数据,同时由于引入了额外的间接层,它们本身也用作标签。这些 类 被制作成 @final 是为了排除作为 Union 实例给出的子 类 标签。 @final 注释启用恒定时间匹配。

请注意,未标记和标记的实现仍然缺少详尽检查,rust 的 match 语句具有。 Python 3.10 附带了一个 match 语句,但我还没有研究 mypy 是否能够用它执行详尽检查。