如何在 Python 枚举中优雅地找到下一个和上一个值?

How can I elegantly find the next and previous value in a Python Enum?

我在 Python 中有一个简单的枚举,看起来像这样:

from enum import Enum

class MyEnum(Enum):
    #All members have increasing non-consecutive integer values.
    A = 0
    B = 2
    C = 10
    D = 18
    ...

我想要函数 pred()succ() 给定 MyEnum 的成员 return 给定元素之前和之后的 MyEnum 的成员,分别(就像 the functions of the same name in Haskell)。例如 succ(MyEnum.B)pred(MyEnum.D) 都应该 return MyEnum.C。如果对第一个成员调用 pred 的最后一个成员调用 succ,则会引发异常。

似乎没有任何内置方法可以执行此操作,虽然我可以调用 iter(MyEnum) 来迭代它必须从头开始遍历整个枚举的值。我可能可以实现一个草率的循环来自己完成这个,但我知道这个网站上有一些真正的 Python 专家,所以我问你:有没有更好的方法?

请注意,您可以在 Enum class:

中提供 succpred 方法
class Sequential(Enum):
    A = 1
    B = 2
    C = 4
    D = 8
    E = 16

    def succ(self):
        v = self.value * 2
        if v > 16:
            raise ValueError('Enumeration ended')
        return Sequential(v)

    def pred(self):
        v = self.value // 2
        if v == 0:
            raise ValueError('Enumeration ended')
        return Sequential(v)

用作:

>>> import myenums
>>> myenums.Sequential.B.succ()
<Sequential.C: 4>
>>> myenums.Sequential.B.succ().succ()
<Sequential.D: 8>
>>> myenums.Sequential.B.succ().succ().pred()
<Sequential.C: 4>

显然,只有当您实际上有一种简单的方法来计算从一个项目到下一个或前一个项目的值时,这才有效,但情况可能并非总是如此。

如果您希望以添加一些 space 为代价获得通用高效的解决方案,您可以构建后继函数和前导函数的映射。 您必须在 class 创建 class 之后将它们添加为属性 (因为 Enum 会弄乱属性),因此您可以使用装饰器来做到这一点:

def add_succ_and_pred_maps(cls):
    succ_map = {}
    pred_map = {}
    cur = None
    nxt = None
    for val in cls.__members__.values():
        if cur is None:
            cur = val
        elif nxt is None:
            nxt = val

        if cur is not None and nxt is not None:
            succ_map[cur] = nxt
            pred_map[nxt] = cur
            cur = nxt
            nxt = None
    cls._succ_map = succ_map
    cls._pred_map = pred_map

    def succ(self):
        return self._succ_map[self]

    def pred(self):
        return self._pred_map[self]

    cls.succ = succ
    cls.pred = pred
    return cls





@add_succ_and_pred_maps
class MyEnum(Enum):
    A = 0
    B = 2
    C = 8
    D = 18

用作:

>>> myenums.MyEnum.A.succ()
<MyEnum.B: 2>
>>> myenums.MyEnum.B.succ()
<MyEnum.C: 8>
>>> myenums.MyEnum.B.succ().pred()
<MyEnum.B: 2>
>>> myenums.MyEnum._succ_map
{<MyEnum.A: 0>: <MyEnum.B: 2>, <MyEnum.C: 8>: <MyEnum.D: 18>, <MyEnum.B: 2>: <MyEnum.C: 8>}

您可能想要一个自定义异常而不是 KeyError 但您明白了。


可能有一种方法可以使用 metaclasses 集成最后一步,但它并不简单,因为 Enums 是使用 metaclasses 实现的,而且它是编写 metaclasses.

并不简单

添加 nextprev 方法(或 succpred)非常简单:

def next(self):
    cls = self.__class__
    members = list(cls)
    index = members.index(self) + 1
    if index >= len(members):
        # to cycle around
        # index = 0
        #
        # to error out
        raise StopIteration('end of enumeration reached')
    return members[index]

def prev(self):
    cls = self.__class__
    members = list(cls)
    index = members.index(self) - 1
    if index < 0:
        # to cycle around
        # index = len(members) - 1
        #
        # to error out
        raise StopIteration('beginning of enumeration reached')
    return members[index]