2016-06-24 7 views
5

Mam proste wyliczenia w Pythonie, który wygląda tak:Jak mogę elegancko znaleźć następną i poprzednią wartość w Enumie Pythona?

from enum import Enum 

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

Chcę funkcje pred() i succ() że dany członek MyEnum zamian członek MyEnum które poprzedza i uda dany element, odpowiednio (podobnie jak the functions of the same name in Haskell). Na przykład succ(MyEnum.B) i pred(MyEnum.D) powinny zwracać zarówno MyEnum.C. Wyjątek można podnieść, jeśli zostanie wywołany succ na ostatnim członku pred na pierwszym członku.

Wydaje się, że nie ma w tym żadnej wbudowanej metody, i chociaż mogę zadzwonić pod numer iter(MyEnum), aby powtórzyć wartości, musi ono przejść od początku do całego wyliczenia. Mogłabym prawdopodobnie zaimplementować niechlujną pętlę, aby to osiągnąć na własną rękę, ale wiem, że na tej stronie jest kilka prawdziwych guru Pythona, więc pytam: czy istnieje lepsze podejście?

+0

Celem 'enum' w Pythonie jest dostarczenie typ danych, które mogą być używane w miejsce„magicznych liczb "i tak naprawdę nie dostarczają matematycznego typu danych odpowiadającego zestawom przeliczalnym. Zatem cel "wyliczenia" Haskella jest nieco inny. W każdym razie nic nie uniemożliwia implementacji 'pred' i' succ' jako metod 'MyEnum'. – Bakuriu

+0

Dostarczono proste tłumaczenie odpowiedzi w tym pytaniu. –

Odpowiedz

2

pamiętać, że można zapewnić succ i pred metod wewnątrz Enum Klasa:

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) 

Używany jako:

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

Oczywiście jest skuteczny tylko wtedy, gdy rzeczywiście mają prosty sposób obliczyć wartości z pozycji do następnej lub poprzedniej, co nie zawsze musi mieć miejsce.

Jeśli chcesz mieć ogólne, wydajne rozwiązanie kosztem dodania przestrzeni, możesz zbudować odwzorowania funkcji następnika i poprzednika. Musisz dodać je jako atrybuty po utworzenie klasy (od Enum bałagan atrybuty), dzięki czemu można używać dekorator, aby to zrobić:

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 

Używany jako:

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

prawdopodobnie chcesz niestandardowy wyjątek zamiast KeyError, ale masz pomysł.


Prawdopodobnie jest to sposób na zintegrowanie ostatni krok używając metaclasses, ale to notstraightforward z prostego faktu, że Enum s realizowane są za pomocą metaclasses i to nie jest trywialne komponować metaclasses.

+0

Twoja odpowiedź była bardzo pomocna na dwa sposoby: rozwiązał mój problem, a rozwiązanie było na tyle skomplikowane, że jestem dość przekonany, że wyliczenie jest złym wyborem struktury danych dla tego, co próbuję zrobić w Pythonie.Dziękuję za Twoją pomoc! – ApproachingDarknessFish

1

dodać next i prev metod (lub succ i pred) jest dość prosta:

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

Zauważ, że metoda 'index' wywołuje' ValueError', gdy element nie został znaleziony, zamiast tego chcesz użyć metody 'find' zamiast' -1'. Również to rozwiązanie wymaga przechodzenia przez wszystkich członków co najmniej raz i prawdopodobnie dwa razy, więc nie jest zbyt efektywne. – Bakuriu

+0

@Bakuriu: '.index()' jest w porządku do użycia w tym przypadku, ponieważ członek będzie zawsze znaleziony. Średnio przechodzi tylko przez połowę członków, i nigdy dwa razy. –

+0

Nie, 'list (cls)' przechodzi przez ** wszystkie ** elementy raz, a następnie 'members.index' przeciętnie przechodzi przez połowę członków, ale wciąż jest więcej niż jeden raz dla każdego członka dla każdego połączenia. Bardziej wydajne jest wykonywanie prostej pętli 'for i, member in enumerate (cls)' i śledzenie poprzedniego elementu każdej iteracji. – Bakuriu

Powiązane problemy