在枚举中允许一组特定的未定义值

问题描述 投票:0回答:1

给定一个这样的枚举:

class Result(IntEnum):
    A = 0 
    B = 1
    C = 3

我希望能够动态创建新的枚举成员,但前提是该值在给定范围内。例如,给定:

class Result(IntEnum, valid_range=(4, 10)):
    A = 0 
    B = 1
    C = 3

我期望出现以下行为:

  • Result(10)
    就可以了,返回一个枚举实例,其中
    .value
    设置为 10。
  • Result(0)
    会回来
    Result.A
  • Result(20)
    Result("hello")
    会引发
    ValueError

为什么?

这适用于 BACnet 标准 - 建筑管理/HVAC 协议。非常简化,它定义了一组映射到整数值的标准消息类型(例如:

READ
WRITE
LIST
)。我将它们建模为枚举:

class MessageType(IntEnum):
    READ = 0
    WRITE = 1
    LIST = 2

然后使用如下:

@dataclass
class Message:
    message_type: MessageType
    data: bytes

def decode(raw: bytes) -> Message:
    message_type = MessageType(unpack("!H", bytes[:2]))
    data = bytes[2:]
    return Message(message_type=message_type, data=data)

该协议还允许供应商定义自己的消息类型,但将其限制为特定的值范围(即 10 到 20 之间)。

考虑的选项:

  • 将每个值显式或动态定义为附加枚举属性(即
    MessageType.VENDOR_10
    ),但有效值的数量可能有数百万,其中大部分不会被使用。
  • 不要使用枚举,只使用
    int
    、int
    TypeAlias
    ,或者只是自定义类。
  • Message
    上的类型注释更改为
    int | MessageType
    ,并在解码时尝试创建
    MessageType
    ,捕获
    ValueError
    并分配 int。
python python-3.x enums
1个回答
0
投票

这是可能的,但需要 Python 3.9 或更高版本,并且确实使用一种当前未记录的数据结构 (

_value2member_map_
):

from enum import IntEnum

class RangedEnum(IntEnum):

    def __repr__(self):
        if self._name_:
            return f"<{self.__class__.__name__}.{self._name_}: {self._value_}>"
        else:
            return f"<{self.__class__.__name__}: {self._value_}>"

    def __init_subclass__(cls, valid_range):
        cls.valid_range = valid_range

    @classmethod
    def _missing_(cls, value):
        start, stop = cls.valid_range
        if start <= value <= stop:
            member = int.__new__(cls, value)
            member._name_ = None
            member._value_ = value
            cls._value2member_map_[value] = member
            return member


class Result(RangedEnum, valid_range=(4, 10)):
    A = 0
    B = 1
    C = 3

和一些简单的测试代码:

>>> for v in (0, 1, 2, 3, 4, 5, 9, 10, 11):
...     try:
...         Result(v)
...     except ValueError:
...         print('nope on', v)
... 
<Result.A: 0>
<Result.B: 1>
nope on 2
<Result.C: 3>
<Result: 4>
<Result: 5>
<Result: 9>
<Result: 10>
nope on 11

1 披露:我是 Python stdlib

Enum
enum34
向后移植
高级枚举 (
aenum
)
库的作者。

© www.soinside.com 2019 - 2024. All rights reserved.