2016-06-24 22 views
5

Ho una semplice enum in Python che assomiglia a questo:Come posso trovare elegantemente il valore successivo e precedente in un 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 
    ... 

voglio funzioni pred() e succ() che, dato un membro della MyEnum ritorno del membro del MyEnum che precede e riesce l'elemento dato, rispettivamente, (proprio come the functions of the same name in Haskell). Ad esempio succ(MyEnum.B) e pred(MyEnum.D) devono entrambi restituire MyEnum.C. È possibile sollevare un'eccezione se succ viene chiamato sull'ultimo membro di pred viene chiamato sul primo membro.

Non sembra esserci alcun metodo incorporato per farlo, e mentre posso chiamare lo iter(MyEnum) per iterare sui valori deve passare attraverso l'intero enum dall'inizio. Probabilmente potrei implementare un loop sciatto per realizzare questo da solo, ma so che ci sono alcuni veri guru Python su questo sito, quindi a voi chiedo: c'è un approccio migliore?

+0

Lo scopo di 'enum' in Python è quello di fornire un tipo di dati che possono essere utilizzati al posto dei "numeri magici "e tale, non proprio per fornire un tipo di dati matematico corrispondente a insiemi enumerabili. Quindi lo scopo dell'enum di Haskell è leggermente diverso. In ogni caso nulla ti impedisce di implementare 'pred' e' succ' come metodi di 'MyEnum'. – Bakuriu

+0

Fornito una semplice traduzione della risposta in questa domanda. –

risposta

2

Si noti che è possibile fornire succ e pred metodi all'interno di un Enum classe:

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) 

Usato come:

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

Ovviamente questo è efficace solo se in realtà hanno un modo semplice per calcolare il valori da un articolo al successivo o precedente, che potrebbe non essere sempre il caso.

Se si desidera una soluzione efficiente generale al costo dell'aggiunta di spazio, è possibile creare i mapping delle funzioni successore e predecessore. È necessario aggiungere questi come attributi dopo la creazione della classe (dal Enum scombina attributi) in modo da poter utilizzare un decoratore per farlo:

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 

Usato come:

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

probabilmente vuoi un'eccezione personalizzata invece di KeyError ma ti viene l'idea.


probabilmente C'è un modo per integrare l'ultimo passo utilizzando metaclassi, ma è notstraightforward per il semplice fatto che Enum s sono realizzati tramite metaclassi e non è banale per comporre metaclassi.

+0

La tua risposta è stata molto utile in due modi: ha risolto il mio problema e, la soluzione era sufficientemente complicata che ora sono abbastanza convinto che un enum è una scelta scadente della struttura dei dati per quello che sto cercando di fare in Python.Grazie per l'aiuto! – ApproachingDarknessFish

1

Aggiunta tuoi next e prev metodi (o succ e pred) è abbastanza facile:

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

Si noti che il metodo 'index' genera un' ValueError' quando l'elemento non è stato trovato, si vuole usare il metodo 'find' invece di ottenere' -1' quando non trovato. Anche questa soluzione richiede di passare attraverso tutti i membri almeno una volta e forse due volte, quindi non è molto efficiente. – Bakuriu

+0

@Bakuriu: '.index()' può essere utilizzato in questo caso perché il membro verrà sempre trovato. In media passa solo per metà dei membri e mai due volte. –

+0

No, 'list (cls)' passa tutti gli ** elementi ** una volta, quindi 'members.index' passa in media metà dei membri, ma è ancora più di una volta per ogni membro per ogni chiamata. È più efficiente eseguire un semplice ciclo 'for i, member in enumerate (cls)' e tenere traccia dell'elemento precedente di ogni iterazione. – Bakuriu

Problemi correlati