给定如下的枚举:

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

Why?

这是针对BACnet标准的,这是一种建筑物管理/暖通空调协议.它大大简化了它定义了一组映射到整数值的标准消息类型(例如:READWRITELIST).我将它们建模为一个枚举:

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之间).

Options considered:

  • 显式地或动态地将每个值定义为一个附加的枚举属性(即MessageType.VENDOR_10),但有效值的数量可以是数百万,其中大多数将不会被使用.
  • 不要使用枚举,只需使用int、int TypeAlias或仅使用自定义类.
  • Message上的类型注释更改为int | MessageType,并在解码时try 创建MessageType,捕获ValueError并分配int.

推荐答案

这是可能的,但需要使用Python3.9或更高版本,并且确实使用了一种当前未记录在案的数据 struct (_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 Enumenum34 backportAdvanced Enumeration (aenum)库的作者.

Python相关问答推荐

使用mySQL的SQlalchemy过滤重叠时间段

Polars LazyFrame在收集后未返回指定的模式顺序

Pystata:从Python并行运行stata实例

scikit-learn导入无法导入名称METRIC_MAPPING64'

如何将Docker内部运行的mariadb与主机上Docker外部运行的Python脚本连接起来

使用groupby Pandas的一些操作

ODE集成中如何终止solve_ivp的无限运行

如何从需要点击/切换的网页中提取表格?

Python Tkinter为特定样式调整所有ttkbootstrap或ttk Button填充的大小,适用于所有主题

递归函数修饰器

什么是一种快速而优雅的方式来转换一个包含一串重复的列,而不对同一个值多次运行转换,

解决Geopandas和Altair中的正图和投影问题

如何训练每一个pandaprame行的线性回归并生成斜率

为什么dict. items()可以快速查找?

合并相似列表

如何为需要初始化的具体类实现依赖反转和接口分离?

如果不使用. to_list()[0],我如何从一个pandas DataFrame中获取一个值?

大型稀疏CSR二进制矩阵乘法结果中的错误

在Django REST框架中定义的URL获得404分

Python键盘模块不会立即检测到按键