2016-06-24 25 views
5

Ich habe eine einfache ENUM in Python, die wie folgt aussieht:Wie kann ich den nächsten und vorherigen Wert in einem Python Enum elegant finden?

from enum import Enum 

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

Funktionen pred() und succ(), die ein Mitglied der MyEnum Rückkehr das Mitglied des MyEnum, die dem gegebenen Element voran I will und es gelingt ihm, bzw. (wie the functions of the same name in Haskell). Zum Beispiel sollten succ(MyEnum.B) und pred(MyEnum.D) beide MyEnum.C zurückgeben. Eine Ausnahme kann ausgelöst werden, wenn succ aufgerufen wird, wenn das letzte Mitglied von pred am ersten Mitglied aufgerufen wird.

Es scheint keine eingebaute Methode zu sein, dies zu tun, und während ich iter(MyEnum) aufrufen kann, um über die Werte zu iterieren, muss es von Anfang an durch die gesamte enum gehen. Ich könnte wahrscheinlich eine schlampige Schleife implementieren, um das alleine zu erreichen, aber ich weiß, dass es auf dieser Seite einige echte Python-Gurus gibt, also frage ich Sie: Gibt es einen besseren Ansatz?

+0

Der Zweck 'enum' in Python ist ein Datentyp zu schaffen, die anstelle von„magischen Zahlen verwendet werden können, "und so, nicht wirklich einen mathematischen Datentyp zu bieten, der aufzählbaren Mengen entspricht. Der Zweck von Haskells "enum" ist also etwas anders. Auf jeden Fall hindert Sie nichts daran, 'pred' und' succ' als Methoden von 'MyEnum' zu implementieren. – Bakuriu

+0

Einfache Übersetzung der Antwort in dieser Frage zur Verfügung gestellt. –

Antwort

2

Beachten Sie, dass succ und pred Methoden innerhalb einer Enum Klasse bieten kann:

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) 

als:

>>> 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> 

Offensichtlich ist dies effizient ist nur, wenn Sie tatsächlich eine einfache Art und Weise haben die zu berechnen Werte von einem Element zum nächsten oder vorhergehenden, was nicht immer der Fall sein kann.

Wenn Sie eine allgemeine effiziente Lösung zum Hinzufügen von Speicherplatz wünschen, können Sie die Zuordnungen der Nachfolger- und Vorgängerfunktionen erstellen. Sie haben diese hinzufügen, wie Attribute nach die Erstellung der Klasse (seit Enum vermasselt Attribute), so können Sie einen Dekorateur verwenden, um das zu tun:

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 

als:

>>> 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>} 

Sie möchten wahrscheinlich eine benutzerdefinierte Ausnahme anstelle von KeyError, aber Sie bekommen die Idee.


Es ist wahrscheinlich ein Weg, um den letzten Schritt mit metaclasses zu integrieren, aber es ist notstraightforward für die einfache Tatsache, dass Enum s verwenden metaclasses implementiert und es ist nicht trivial metaclasses zu komponieren.

+0

Ihre Antwort war in zweierlei Hinsicht sehr hilfreich: Sie löste mein Problem, und die Lösung war ausreichend kompliziert, sodass ich jetzt ziemlich überzeugt bin, dass ein Enum eine schlechte Wahl der Datenstruktur für das ist, was ich in Python zu tun versuche.Danke für deine Hilfe! – ApproachingDarknessFish

1

Ihre next und prev Methoden Hinzufügen (oder succ und pred) ist einfach genug:

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] 
+0

Beachten Sie, dass die Methode 'index' einen' ValueError' auslöst, wenn das Element nicht gefunden wurde. Sie möchten stattdessen die Methode 'find' verwenden, um' -1' zu erhalten, wenn sie nicht gefunden wird. Auch diese Lösung erfordert, dass alle Mitglieder mindestens einmal und möglicherweise zweimal durchlaufen werden, ist also nicht wirklich effizient. – Bakuriu

+0

@Bakuriu: '.index()' ist in diesem Fall in Ordnung, weil das Mitglied immer gefunden wird. Im Durchschnitt geht es nur durch die Hälfte der Mitglieder und niemals zweimal. –

+0

Nein, 'list (cls)' geht einmal durch ** alle ** Elemente, und dann geht 'members.index' durchschnittlich durch die Hälfte der Mitglieder, aber es ist immer noch mehr als einmal für jedes Mitglied für jeden Anruf. Es ist effizienter, eine einfache "for i, member in enumerate (cls)" - Schleife auszuführen und das vorherige Element jeder Iteration zu verfolgen. – Bakuriu