给定一个这样的枚举:
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 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
) 库的作者。