如何在 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:
中提供 succ
和 pred
方法
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 集成最后一步,但它并不简单,因为 Enum
s 是使用 metaclasses 实现的,而且它是编写 metaclasses.
并不简单
添加 next
和 prev
方法(或 succ
和 pred
)非常简单:
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]
我在 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:
succ
和 pred
方法
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 集成最后一步,但它并不简单,因为 Enum
s 是使用 metaclasses 实现的,而且它是编写 metaclasses.
添加 next
和 prev
方法(或 succ
和 pred
)非常简单:
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]