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

时间:2016-06-24 06:42:04

标签: python python-3.x enums next

我在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分别返回给定元素之前和之后的MyEnum成员(就像{{succ(MyEnum.B) 3}})。例如,pred(MyEnum.D)MyEnum.C都应返回succ。如果在第一个成员上调用pred的最后一个成员时调用iter(MyEnum),则可以引发异常。

似乎没有任何内置方法可以做到这一点,虽然我可以调用Promise迭代值,但它必须从头开始遍历整个枚举。我本可以实现一个草率循环来实现这个目标,但我知道这个网站上有一些真正的Python大师,所以我问你:有更好的方法吗?

2 个答案:

答案 0 :(得分:4)

请注意,您可以在succ类中提供predEnum方法:

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>

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

如果您想要以增加一些空间为代价来获得通用的高效解决方案,则可以构建后继函数和前任函数的映射。 你必须在创建类之后将这些作为属性添加(因为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,但你明白了。

可能有一种方法可以使用元类集成最后一步,但是对于Enum是使用元类实现的简单事实而言它并不简单,并且编写元类并不容易。

答案 1 :(得分:1)

添加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]