微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决用特定类型的值表示 Enum 的正确方法

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

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

现在我如何将这些类型关联到相应的值类型?我的意思是,如果一个函数返回一个 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 中,您可以定义一个 Enum,其中每个类型都有参数:

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。这是因为没有与未标记联合的变体一起存储的标记,我们可以通过这些标记来索引地图/表。进行恒定时间匹配的幼稚尝试可能如下所示:

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,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 注释不是必需的,它只是为我随后在 __init__ 函数中使用的标签创建一个 get

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。