Come posso rappresentare un 'Enum' in Python?


1143

Sono principalmente uno sviluppatore C #, ma attualmente sto lavorando a un progetto in Python.

Come posso rappresentare l'equivalente di un Enum in Python?

Risposte:


2689

Enum sono stati aggiunti a Python 3.4 come descritto in PEP 435 . È stato anche eseguito il backport su 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4 su pypi.

Per le tecniche Enum più avanzate, prova la libreria aenum (2.7, 3.3+, stesso autore di enum34. Code non è perfettamente compatibile tra py2 e py3, ad esempio __order__in Python 2 ).

  • Per usare enum34, fare$ pip install enum34
  • Per usare aenum, fare$ pip install aenum

L'installazione enum(senza numeri) installerà una versione completamente diversa e incompatibile.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

o equivalentemente:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

Nelle versioni precedenti, un modo per realizzare enum è:

def enum(**enums):
    return type('Enum', (), enums)

che viene utilizzato in questo modo:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

Puoi anche facilmente supportare l'enumerazione automatica con qualcosa del genere:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

e usato così:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

Il supporto per riconvertire i valori in nomi può essere aggiunto in questo modo:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

Questo sovrascrive qualsiasi cosa con quel nome, ma è utile per rendere i tuoi enumeratori in output. Genererà KeyError se la mappatura inversa non esiste. Con il primo esempio:

>>> Numbers.reverse_mapping['three']
'THREE'

1
Non sono stato in grado di capire, perché hanno superato i kwarg (** nominato) nel metodo enum (* sequenziale, ** nominato)? Per favore, spiega. Anche senza kwargs funzionerà. L'ho controllato.
Seenu S

Sarebbe bello aggiornare la funzione Python 2 per essere compatibile con l'API funzionale di Enum (nome, valori) di Python 3
bscan

Il var kwargs ( **named) nella funzione enum per le versioni precedenti supporta i valori personalizzati:enum("blue", "red", "green", black=0)
Éric Araujo,

823

Prima di PEP 435, Python non aveva un equivalente ma potevi implementare il tuo.

Io stesso, mi piace mantenerlo semplice (ho visto alcuni esempi orribilmente complessi in rete), qualcosa del genere ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

In Python 3.4 ( PEP 435 ), puoi rendere Enum la classe base. Questo ti dà un po 'di funzionalità extra, descritte nel PEP. Ad esempio, i membri enum sono distinti dagli interi e sono composti da a namee a value.

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

Se non si desidera digitare i valori, utilizzare la seguente scorciatoia:

class Animal(Enum):
    DOG, CAT = range(2)

Enumle implementazioni possono essere convertite in elenchi e sono iterabili . L'ordine dei suoi membri è l'ordine di dichiarazione e non ha nulla a che fare con i loro valori. Per esempio:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

51
No, è una variabile di classe.
Georg Schölly,

246
Python è dinamico per impostazione predefinita. Non esiste alcun motivo valido per imporre la sicurezza in fase di compilazione in un linguaggio come Python, soprattutto quando non ce n'è. E un'altra cosa ... un buon modello è buono solo nel contesto in cui è stato creato. Un buon modello può anche essere sostituito o completamente inutile, a seconda degli strumenti che stai utilizzando.
Alexandru Nedelcu,

20
@Longpoke se hai 100 valori, allora stai sicuramente facendo qualcosa di sbagliato;) Mi piacciono i numeri associati ai miei enum ... sono facili da scrivere (vs stringhe), possono essere facilmente persistenti in un database e sono compatibili con l'enum C / C ++, che facilita il marshalling.
Alexandru Nedelcu,

50
Lo uso, con i numeri sostituiti da object().
Tobu,

9
L'originale PEP354 non viene più semplicemente rifiutato, ma ora è contrassegnato come sostituito. PEP435 aggiunge un Enum standard per Python 3.4. Vedi python.org/dev/peps/pep-0435
Peter Hansen,

323

Ecco una implementazione:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Ecco il suo utilizzo:

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)

51
Eccellente. Questo può essere ulteriormente migliorato sovrascrivendo __setattr__(self, name, value)e forse in __delattr__(self, name)modo che se scrivi accidentalmente Animals.DOG = CAT, non ci riuscirà silenziosamente.
Joonas Pulakka,

15
@shahjapan: interessante, ma relativamente lento: viene eseguito un test per ogni accesso come Animals.DOG; inoltre, i valori delle costanti sono stringhe, quindi i confronti con queste costanti sono più lenti di se, diciamo, i numeri interi fossero consentiti come valori.
Eric O Lebigot,

3
@shahjapan: direi che questa soluzione non è leggibile come le soluzioni più brevi di Alexandru o Mark, per esempio. È una soluzione interessante, comunque. :)
Eric O Lebigot,

Ho provato a utilizzare la setattr()funzione all'interno del __init__()metodo anziché il __getattr__()metodo overidding . Suppongo che questo dovrebbe funzionare allo stesso modo: class Enum (oggetto): def __init __ (self, enum_string_list): if type (enum_string_list) == list: for enum_string in enum_string_list: setattr (self, enum_string, enum_string) else: raise AttributeError
Harshith JV,

8
@ AndréTerra: come si controlla l'appartenenza impostata in un try-exceptblocco?
bgusach,

210

Se hai bisogno dei valori numerici, ecco il modo più veloce:

dog, cat, rabbit = range(3)

In Python 3.x puoi anche aggiungere un segnaposto stellato alla fine, che assorbirà tutti i valori rimanenti dell'intervallo nel caso in cui non ti dispiaccia sprecare memoria e non puoi contare:

dog, cat, rabbit, horse, *_ = range(100)

1
Ma questo potrebbe richiedere più memoria!
MJ,

Non vedo il punto del segnaposto stellato dato che Python controllerà il numero di valori da decomprimere (quindi farà il conteggio per te).
Gabriel Devillers,

@GabrielDevillers, penso che Python solleverà un'eccezione se c'è una mancata corrispondenza sul numero di elementi nella tupla da assegnare.
Mark Harrison,

1
Infatti, lo fa nel mio test (Python2,3) ma ciò significa che qualsiasi errore di conteggio da parte del programmatore verrà colto al primo test (con un messaggio che indica il conteggio corretto).
Gabriel Devillers,

1
Non posso contare. Il segnaposto stellato può anche aggiustare le mie finanze?
javadba,

131

La migliore soluzione per te dipenderebbe da ciò di cui hai bisogno dal tuo falso enum .

Enum semplice:

Se hai bisogno del enumsolo come un elenco di nomi che identificano elementi diversi , la soluzione di Mark Harrison (sopra) è eccezionale:

Pen, Pencil, Eraser = range(0, 3)

L'uso di a rangeconsente inoltre di impostare qualsiasi valore iniziale :

Pen, Pencil, Eraser = range(9, 12)

Oltre a quanto sopra, se si richiede anche che gli articoli appartengano a un contenitore di qualche tipo, incorporarli in una classe:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Per usare l'elemento enum, ora dovresti usare il nome del contenitore e il nome dell'articolo:

stype = Stationery.Pen

Enum complesso:

Per lunghe liste di enum o usi più complicati di enum, queste soluzioni non saranno sufficienti. Potresti guardare la ricetta di Will Ware per la simulazione delle enumerazioni in Python pubblicata nel Cookbook di Python . Una versione online è disponibile qui .

Ulteriori informazioni:

PEP 354: le enumerazioni in Python hanno i dettagli interessanti di una proposta di enum in Python e perché è stata respinta.


7
con rangete puoi omettere il primo argomento se è 0
ToonAlfrink il

Un altro enum falso adatto ad alcuni scopi è my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Quindi my_enumpuò essere utilizzato nella ricerca, ad esempio, my_enum['Item0']può essere un indice in una sequenza. Potresti voler racchiudere il risultato str.splitin una funzione che genera un'eccezione se ci sono duplicati.
Ana Nimbus,

Bello! Per le bandiere puoiFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx,

Questa è la risposta migliore
Yuriy Pozniak

78

Il modello di enumerazione typesafe utilizzato in Java pre-JDK 5 presenta numerosi vantaggi. Proprio come nella risposta di Alexandru, crei una classe e i campi a livello di classe sono i valori enum; tuttavia, i valori enum sono istanze della classe piuttosto che piccoli numeri interi. Questo ha il vantaggio che i tuoi valori enum non si confrontano inavvertitamente uguali a numeri interi piccoli, puoi controllare come vengono stampati, aggiungere metodi arbitrari se è utile e fare affermazioni usando isinstance:

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")

>>> x = Animal.DOG
>>> x
<Animal: dog>
>>> x == 1
False

Un recente thread su python-dev ha sottolineato che ci sono un paio di librerie enum in natura, tra cui:


16
Penso che questo sia un approccio molto negativo. Animal.DOG = Animal ("dog") Animal.DOG2 = Animal ("dog") asserisce Animal.DOG == Animal.DOG2 fallisce ...
Confusione

11
@Confusion L'utente non dovrebbe chiamare il costruttore, il fatto che ci sia anche un costruttore è un dettaglio dell'implementazione e devi comunicare a chi sta usando il tuo codice che fare nuovi valori di enumerazione non ha senso e che il codice uscente non lo farà "fare la cosa giusta". Ovviamente ciò non ti impedisce di implementare Animal.from_name ("dog") -> Animal.DOG.
Aaron Maenpaa,

13
"il vantaggio che i tuoi valori enum non si confrontano inavvertitamente uguale a piccoli numeri interi" Qual è il vantaggio in questo? Cosa c'è di sbagliato nel confrontare il tuo enum con numeri interi? Soprattutto se memorizzi l'enum nel database, di solito vuoi che sia archiviato come numeri interi, quindi dovrai confrontarlo con numeri interi a un certo punto.
ibz,

3
@Aaaron Maenpaa. corretta. È ancora un modo rotto e troppo complicato per farlo.
aaronasterling,

4
@AaronMcSmooth Dipende molto dal fatto che tu stia arrivando dalla prospettiva C di "Enums sono solo nomi per un paio di ints" o l'approccio più orientato agli oggetti in cui i valori enum sono oggetti reali e hanno metodi (che è come gli enum in Java 1.5 sono, e quale era il tipo di enum pattern sicuro). Personalmente, non mi piacciono le istruzioni switch quindi mi sposto verso valori enum che sono oggetti reali.
Aaron Maenpaa,

61

Una classe Enum può essere una linea.

class Enum(tuple): __getattr__ = tuple.index

Come usarlo (ricerca avanti e indietro, chiavi, valori, elementi, ecc.)

>>> State = Enum(['Unclaimed', 'Claimed'])
>>> State.Claimed
1
>>> State[1]
'Claimed'
>>> State
('Unclaimed', 'Claimed')
>>> range(len(State))
[0, 1]
>>> [(k, State[k]) for k in range(len(State))]
[(0, 'Unclaimed'), (1, 'Claimed')]
>>> [(k, getattr(State, k)) for k in State]
[('Unclaimed', 0), ('Claimed', 1)]

Penso che sia la soluzione più elegante fine più semplice. In python 2.4 (sì, vecchio server legacy) le tuple non sono indicizzate. Ho risolto la sostituzione con l'elenco.
Massimo

Ho provato questo in un notebook Jupyter e ho scoperto che non avrebbe funzionato come una definizione di una riga, ma che sarebbe stato accettato l' inserimento della definizione di getattr su una seconda riga (rientrata).
user5920660

Questa soluzione mi consente di utilizzare la inparola chiave per cercare membri che siano accurati. Esempio di utilizzo:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini,

51

Quindi, sono d'accordo. Non imponiamo la sicurezza dei tipi in Python, ma vorrei proteggermi da errori sciocchi. Quindi cosa ne pensiamo?

class Animal(object):
    values = ['Horse','Dog','Cat']

    class __metaclass__(type):
        def __getattr__(self, name):
            return self.values.index(name)

Mi impedisce la collisione di valore nel definire i miei enum.

>>> Animal.Cat
2

C'è un altro vantaggio utile: ricerche inverse molto veloci:

def name_of(self, i):
    return self.values[i]

Mi piace questo, ma potresti anche bloccare i valori per l'efficienza con una tupla? Ci ho provato e ho trovato una versione che imposta self.values ​​da args in init . È bello poter dichiarare Animal = Enum('horse', 'dog', 'cat'). Prendo anche ValueError in getattr in caso di un elemento mancante in self.values ​​- sembra meglio invece sollevare un AttributeError con la stringa del nome fornita. Non sono riuscito a far funzionare la metaclasse in Python 2.7 in base alle mie conoscenze limitate in quell'area, ma la mia classe Enum personalizzata funziona perfettamente con metodi di istanza semplici.
trojjer,

49

Python non ha un equivalente incorporato enume altre risposte hanno idee per implementare il tuo (potresti anche essere interessato alla versione superiore del ricettario Python).

Tuttavia, nelle situazioni in cui un enumsarebbe richiesto in C, di solito finisco solo usando stringhe semplici : a causa del modo in cui gli oggetti / attributi sono implementati, (C) Python è ottimizzato per funzionare molto velocemente con stringhe brevi comunque, quindi non sarà davvero alcun vantaggio in termini di prestazioni nell'uso di numeri interi. Per evitare errori di battitura / valori non validi, è possibile inserire assegni in posizioni selezionate.

ANIMALS = ['cat', 'dog', 'python']

def take_for_a_walk(animal):
    assert animal in ANIMALS
    ...

(Uno svantaggio rispetto all'utilizzo di una classe è la perdita del vantaggio del completamento automatico)


2
Preferisco questa soluzione. Mi piace usare i tipi integrati ove possibile.
Seun Osewa,

Quella versione non è davvero esagerata. Ha solo un sacco di codice di prova fornito
Casebash,

1
In realtà, la versione "corretta" è nei commenti ed è molto più complessa - la versione principale ha un bug minore.
Casebash,

39

Il 10/05/2013, Guido ha accettato di accettare PEP 435 nella libreria standard di Python 3.4. Ciò significa che Python ha finalmente il supporto integrato per le enumerazioni!

È disponibile un backport per Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4. È su Pypi come enum34 .

Dichiarazione:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

Rappresentazione:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

Iterazione:

>>> for color in Color:
...   print(color)
...
Color.red
Color.green
Color.blue

Accesso programmatico:

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

Per ulteriori informazioni, consultare la proposta . La documentazione ufficiale probabilmente seguirà presto.


33

Preferisco definire gli enum in Python in questo modo:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

È più a prova di bug rispetto all'uso di numeri interi poiché non devi preoccuparti di garantire che i numeri interi siano univoci (ad esempio, se hai detto Dog = 1 e Cat = 1 verrai fregato).

È più a prova di bug rispetto all'uso delle stringhe poiché non devi preoccuparti di errori di battitura (ad esempio x == "catt" fallisce silenziosamente, ma x == Animal.Catt è un'eccezione di runtime).


31
def M_add_class_attribs(attribs):
    def foo(name, bases, dict_):
        for v, k in attribs:
            dict_[k] = v
        return type(name, bases, dict_)
    return foo

def enum(*names):
    class Foo(object):
        __metaclass__ = M_add_class_attribs(enumerate(names))
        def __setattr__(self, name, value):  # this makes it read-only
            raise NotImplementedError
    return Foo()

Usalo in questo modo:

Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.DOG = 2 # raises NotImplementedError

se vuoi solo simboli univoci e non ti preoccupi dei valori, sostituisci questa riga:

__metaclass__ = M_add_class_attribs(enumerate(names))

con questo:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)

11
IMHO sarebbe più pulito se è stato modificato enum(names)per enum(*names)- allora si potrebbe eliminare la parentesi in più quando si chiama esso.
Chris Lutz,

Mi piace questo approccio. In realtà l'ho modificato per impostare il valore dell'attributo sulla stessa stringa del nome, che ha la bella proprietà Animal.DOG == 'DOG', quindi si stringono automaticamente per te. (Aiuta immensamente per la stampa dell'output di debug.)
Ted Mielczarek,

23

Da Python 3.4 ci sarà il supporto ufficiale per enum. Puoi trovare documentazione ed esempi qui sulla pagina della documentazione di Python 3.4 .

Le enumerazioni vengono create utilizzando la sintassi della classe, che le rende facili da leggere e scrivere. Un metodo di creazione alternativo è descritto nell'API funzionale. Per definire un'enumerazione, sottoclasse Enum come segue:

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3

Anche il back porting è ora supportato. Questa è la strada da percorrere.
srock

22

Hmmm ... Suppongo che la cosa più vicina a un enum sarebbe un dizionario, definito in questo modo:

months = {
    'January': 1,
    'February': 2,
    ...
}

o

months = dict(
    January=1,
    February=2,
    ...
)

Quindi, puoi usare il nome simbolico per le costanti in questo modo:

mymonth = months['January']

Esistono altre opzioni, come un elenco di tuple o una tupla di tuple, ma il dizionario è l'unico che ti fornisce un modo "simbolico" (stringa costante) per accedere al valore.

Modifica: mi piace anche la risposta di Alexandru!


E soprattutto puoi facilmente iterare su un dizionario se devi accedere ai suoi valori come se avessi bisogno dei suoi valori stringa per apparire come elementi della casella combinata. Quindi usa un dizionario in sostituzione delle enumerazioni.
LEMUEL ADANE,

22

Un'altra, molto semplice, implementazione di un enum in Python, usando namedtuple:

from collections import namedtuple

def enum(*keys):
    return namedtuple('Enum', keys)(*keys)

MyEnum = enum('FOO', 'BAR', 'BAZ')

o, in alternativa,

# With sequential number values
def enum(*keys):
    return namedtuple('Enum', keys)(*range(len(keys)))

# From a dict / keyword args
def enum(**kwargs):
    return namedtuple('Enum', kwargs.keys())(*kwargs.values())

Come il metodo sopra che sottoclassi set, questo consente:

'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO

Ma ha una maggiore flessibilità in quanto può avere chiavi e valori diversi. Questo permette

MyEnum.FOO < MyEnum.BAR

agire come previsto se si utilizza la versione che inserisce valori numerici sequenziali.


20

Quello che uso:

class Enum(object):
    def __init__(self, names, separator=None):
        self.names = names.split(separator)
        for value, name in enumerate(self.names):
            setattr(self, name.upper(), value)
    def tuples(self):
        return tuple(enumerate(self.names))

Come usare:

>>> state = Enum('draft published retracted')
>>> state.DRAFT
0
>>> state.RETRACTED
2
>>> state.FOO
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'Enum' object has no attribute 'FOO'
>>> state.tuples()
((0, 'draft'), (1, 'published'), (2, 'retracted'))

Quindi questo ti dà costanti intere come state.PUBLISHED e le due tuple da usare come scelte nei modelli Django.


17

davidg consiglia di usare i dadi. Farei un ulteriore passo avanti e utilizzerei i set:

months = set('January', 'February', ..., 'December')

Ora puoi verificare se un valore corrisponde a uno dei valori nel set in questo modo:

if m in months:

come dF, però, di solito uso solo costanti di stringa al posto di enumerazioni.


sì! Molto meglio se erediti set e fornisci il metodo getattr !
Shahjapan,

17

Mantienilo semplice:

class Enum(object): 
    def __init__(self, tupleList):
            self.tupleList = tupleList

    def __getattr__(self, name):
            return self.tupleList.index(name)

Poi:

DIRECTION = Enum(('UP', 'DOWN', 'LEFT', 'RIGHT'))
DIRECTION.DOWN
1

16

Questo è il migliore che abbia mai visto: "First Class Enums in Python"

http://code.activestate.com/recipes/413486/

Ti dà una classe e la classe contiene tutti gli enum. Gli enum possono essere confrontati tra loro, ma non hanno alcun valore particolare; non puoi usarli come valore intero. (All'inizio ho resistito perché sono abituato a C enum, che sono valori interi. Ma se non puoi usarlo come intero, non puoi usarlo come intero per errore, quindi nel complesso penso che sia una vittoria .) Ogni enum ha un valore unico. Puoi stampare enum, puoi iterare su di essi, puoi verificare che un valore enum sia "dentro" l'enum. È abbastanza completo e lucido.

Modifica (cfi): il link sopra non è compatibile con Python 3. Ecco il mio port di enum.py su Python 3:

def cmp(a,b):
   if a < b: return -1
   if b < a: return 1
   return 0


def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __iter__(self):        return iter(constants)
      def __len__(self):         return len(constants)
      def __getitem__(self, i):  return constants[i]
      def __repr__(self):        return 'Enum' + str(names)
      def __str__(self):         return 'enum ' + str(constants)

   class EnumValue(object):
      __slots__ = ('__value')
      def __init__(self, value): self.__value = value
      Value = property(lambda self: self.__value)
      EnumType = property(lambda self: EnumType)
      def __hash__(self):        return hash(self.__value)
      def __cmp__(self, other):
         # C fans might want to remove the following assertion
         # to make all enums comparable by ordinal value {;))
         assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
         return cmp(self.__value, other.__value)
      def __lt__(self, other):   return self.__cmp__(other) < 0
      def __eq__(self, other):   return self.__cmp__(other) == 0
      def __invert__(self):      return constants[maximum - self.__value]
      def __nonzero__(self):     return bool(self.__value)
      def __repr__(self):        return str(names[self.__value])

   maximum = len(names) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = tuple(constants)
   EnumType = EnumClass()
   return EnumType


if __name__ == '__main__':
   print( '\n*** Enum Demo ***')
   print( '--- Days of week ---')
   Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')
   print( Days)
   print( Days.Mo)
   print( Days.Fr)
   print( Days.Mo < Days.Fr)
   print( list(Days))
   for each in Days:
      print( 'Day:', each)
   print( '--- Yes/No ---')
   Confirmation = Enum('No', 'Yes')
   answer = Confirmation.No
   print( 'Your answer is not', ~answer)

Questa ricetta è stata utilizzata come base per un PEP, che è stato respinto. python.org/dev/peps/pep-0354 Un'estensione che mi piace: i valori enum dovrebbero avere una variabile membro che ti consenta di ottenere il valore intero interno. Non dovrebbe essere possibile eseguire il cast di un enum su un numero intero per errore, quindi il .__int__()metodo dovrebbe sollevare un'eccezione per un enum; ma dovrebbe esserci un modo per ottenere il valore. E dovrebbe essere possibile impostare valori interi specifici al momento della definizione della classe, in modo da poter usare un enum per cose come le costanti nel statmodulo.
Steveha,

14

Ho avuto occasione di aver bisogno di una classe Enum, allo scopo di decodificare un formato di file binario. Le caratteristiche che mi è capitato di desiderare sono la definizione concisa dell'enum, la possibilità di creare liberamente istanze dell'enum con un valore intero o una stringa e una utile represpressione. Ecco cosa ho finito con:

>>> class Enum(int):
...     def __new__(cls, value):
...         if isinstance(value, str):
...             return getattr(cls, value)
...         elif isinstance(value, int):
...             return cls.__index[value]
...     def __str__(self): return self.__name
...     def __repr__(self): return "%s.%s" % (type(self).__name__, self.__name)
...     class __metaclass__(type):
...         def __new__(mcls, name, bases, attrs):
...             attrs['__slots__'] = ['_Enum__name']
...             cls = type.__new__(mcls, name, bases, attrs)
...             cls._Enum__index = _index = {}
...             for base in reversed(bases):
...                 if hasattr(base, '_Enum__index'):
...                     _index.update(base._Enum__index)
...             # create all of the instances of the new class
...             for attr in attrs.keys():
...                 value = attrs[attr]
...                 if isinstance(value, int):
...                     evalue = int.__new__(cls, value)
...                     evalue._Enum__name = attr
...                     _index[value] = evalue
...                     setattr(cls, attr, evalue)
...             return cls
... 

Un esempio stravagante di usarlo:

>>> class Citrus(Enum):
...     Lemon = 1
...     Lime = 2
... 
>>> Citrus.Lemon
Citrus.Lemon
>>> 
>>> Citrus(1)
Citrus.Lemon
>>> Citrus(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __new__
KeyError: 5
>>> class Fruit(Citrus):
...     Apple = 3
...     Banana = 4
... 
>>> Fruit.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True

Caratteristiche principali:

  • str(), int()E repr()tutti i prodotti all'uscita più utile possibile, rispettivamente il nome del enumartion, il suo valore intero, e un'espressione Python che restituisce indietro al conteggio.
  • I valori enumerati restituiti dal costruttore sono limitati rigorosamente ai valori predefiniti, nessun valore enum accidentale.
  • I valori enumerati sono singleton; possono essere rigorosamente confrontati conis

Mi piace molto l'uso di una superclasse con la sua metaclasse, per semplificare la definizione di enumerazioni. Quello che manca qui è un metodo __contains__. Vorrei essere in grado di verificare che una determinata variabile faccia parte dell'enum, principalmente perché voglio gli enum per i valori consentiti di un parametro di funzione.
xorsyst

Questa è in realtà una versione leggermente ritagliata di quella che ho creato originariamente (che puoi trovare qui: enum_strict.py ) v che definisce un __instancecheck__metodo. Le classi non sono raccolte di istanze, quindi 1 in Fruitè assurdo. Tuttavia, la versione collegata supporta isinstance(1, Fruit)quale sarebbe più corretta in termini di nozione di classi e istanze.
SingleNegationElimination

Ma dimenticare le lezioni e pensare in termini di enumerazioni, quindi ha senso pensarle come una raccolta. Ad esempio, potrei avere un elenco di modalità di apertura dei file (MODE.OPEN, MODE.WRITE, ecc.). Voglio verificare i parametri della mia funzione: se mode in MODE: legge molto meglio di isintance (mode, Mode)
xorsyst

Ho messo su qualcosa di molto simile, che supporta più di semplici ints, ed è documentato e testato, su GitHub: github.com/hmeine/named_constants
hans_meine,

12

Il nuovo standard in Python è PEP 435 , quindi una classe Enum sarà disponibile nelle versioni future di Python:

>>> from enum import Enum

Tuttavia, per iniziare ad usarlo ora puoi installare la libreria originale che ha motivato il PEP:

$ pip install flufl.enum

Quindi puoi usarlo secondo la sua guida online :

>>> from flufl.enum import Enum
>>> class Colors(Enum):
...     red = 1
...     green = 2
...     blue = 3
>>> for color in Colors: print color
Colors.red
Colors.green
Colors.blue

10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Se lo chiami, è un tuo problema, ma se non crei oggetti invece di valori ti consente di farlo:

>>> DOG = enum('BARK', 'WALK', 'SIT')
>>> CAT = enum('MEOW', 'WALK', 'SIT')
>>> DOG.WALK == CAT.WALK
False

Quando si usano altre implementazioni qui (anche quando si usano istanze denominate nel mio esempio), si deve essere certi di non provare mai a confrontare oggetti di enumerazioni diverse. Ecco una possibile trappola:

>>> DOG = enum('BARK'=1, 'WALK'=2, 'SIT'=3)
>>> CAT = enum('WALK'=1, 'SIT'=2)
>>> pet1_state = DOG.BARK
>>> pet2_state = CAT.WALK
>>> pet1_state == pet2_state
True

Yikes!


9

Mi piace molto la soluzione di Alec Thomas (http://stackoverflow.com/a/1695250):

def enum(**enums):
    '''simple constant "enums"'''
    return type('Enum', (object,), enums)

È elegante e pulito, ma è solo una funzione che crea una classe con gli attributi specificati.

Con una piccola modifica alla funzione, possiamo farla agire un po 'più "enumy":

NOTA: ho creato i seguenti esempi provando a riprodurre il comportamento del nuovo stile 'enums' di pygtk (come Gtk.MessageType.WARNING)

def enum_base(t, **enums):
    '''enums with a base class'''
    T = type('Enum', (t,), {})
    for key,val in enums.items():
        setattr(T, key, T(val))

    return T

Questo crea un enum basato su un tipo specificato. Oltre a fornire l'accesso agli attributi come la funzione precedente, si comporta come ci si aspetterebbe da un Enum rispetto ai tipi. Inoltre eredita la classe base.

Ad esempio, enumerazione di numeri interi:

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> type(Numbers)
<type 'type'>
>>> type(Numbers.ONE)
<class 'Enum'>
>>> isinstance(x, Numbers)
True

Un'altra cosa interessante che può essere fatta con questo metodo è personalizzare il comportamento specifico sovrascrivendo i metodi integrati:

def enum_repr(t, **enums):
    '''enums with a base class and repr() output'''
    class Enum(t):
        def __repr__(self):
            return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)

    for key,val in enums.items():
        i = Enum(val)
        i._name = key
        setattr(Enum, key, i)

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'

questa idea di tipo "base" è ordinata :)
MestreLion,

sì, nota che puoi farlo anche con il nuovo Enum di Python 3.4: python.org/dev/peps/pep-0435/#other-derived-enumerations
bj0

7

Il pacchetto enum di PyPI fornisce una solida implementazione di enum. Una risposta precedente menzionava PEP 354; questo è stato respinto ma la proposta è stata implementata http://pypi.python.org/pypi/enum .

L'utilizzo è semplice ed elegante:

>>> from enum import Enum
>>> Colors = Enum('red', 'blue', 'green')
>>> shirt_color = Colors.green
>>> shirt_color = Colors[2]
>>> shirt_color > Colors.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'

5

Il suggerimento di Alexandru di usare costanti di classe per gli enum funziona abbastanza bene.

Mi piace anche aggiungere un dizionario per ogni set di costanti per cercare una rappresentazione di stringa leggibile dall'uomo.

Questo ha due scopi: a) fornisce un modo semplice per stampare in modo carino il tuo enum eb) il dizionario raggruppa logicamente le costanti in modo da poter verificare l'appartenenza.

class Animal:    
  TYPE_DOG = 1
  TYPE_CAT = 2

  type2str = {
    TYPE_DOG: "dog",
    TYPE_CAT: "cat"
  }

  def __init__(self, type_):
    assert type_ in self.type2str.keys()
    self._type = type_

  def __repr__(self):
    return "<%s type=%s>" % (
        self.__class__.__name__, self.type2str[self._type].upper())

5

Ecco un approccio con alcune caratteristiche diverse che trovo preziose:

  • consente> e <confronto basato sull'ordine in enum, non sull'ordine lessicale
  • può indirizzare l'elemento per nome, proprietà o indice: xa, x ['a'] o x [0]
  • supporta operazioni di slicing come [:] o [-1]

e soprattutto impedisce il confronto tra enumerazioni di diversi tipi !

Basato su http://code.activestate.com/recipes/413486-first-class-enums-in-python .

Molti doctest inclusi qui per illustrare cosa c'è di diverso in questo approccio.

def enum(*names):
    """
SYNOPSIS
    Well-behaved enumerated type, easier than creating custom classes

DESCRIPTION
    Create a custom type that implements an enumeration.  Similar in concept
    to a C enum but with some additional capabilities and protections.  See
    http://code.activestate.com/recipes/413486-first-class-enums-in-python/.

PARAMETERS
    names       Ordered list of names.  The order in which names are given
                will be the sort order in the enum type.  Duplicate names
                are not allowed.  Unicode names are mapped to ASCII.

RETURNS
    Object of type enum, with the input names and the enumerated values.

EXAMPLES
    >>> letters = enum('a','e','i','o','u','b','c','y','z')
    >>> letters.a < letters.e
    True

    ## index by property
    >>> letters.a
    a

    ## index by position
    >>> letters[0]
    a

    ## index by name, helpful for bridging string inputs to enum
    >>> letters['a']
    a

    ## sorting by order in the enum() create, not character value
    >>> letters.u < letters.b
    True

    ## normal slicing operations available
    >>> letters[-1]
    z

    ## error since there are not 100 items in enum
    >>> letters[99]
    Traceback (most recent call last):
        ...
    IndexError: tuple index out of range

    ## error since name does not exist in enum
    >>> letters['ggg']
    Traceback (most recent call last):
        ...
    ValueError: tuple.index(x): x not in tuple

    ## enums must be named using valid Python identifiers
    >>> numbers = enum(1,2,3,4)
    Traceback (most recent call last):
        ...
    AssertionError: Enum values must be string or unicode

    >>> a = enum('-a','-b')
    Traceback (most recent call last):
        ...
    TypeError: Error when calling the metaclass bases
        __slots__ must be identifiers

    ## create another enum
    >>> tags = enum('a','b','c')
    >>> tags.a
    a
    >>> letters.a
    a

    ## can't compare values from different enums
    >>> letters.a == tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    >>> letters.a < tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    ## can't update enum after create
    >>> letters.a = 'x'
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'a' is read-only

    ## can't update enum after create
    >>> del letters.u
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'u' is read-only

    ## can't have non-unique enum values
    >>> x = enum('a','b','c','a')
    Traceback (most recent call last):
        ...
    AssertionError: Enums must not repeat values

    ## can't have zero enum values
    >>> x = enum()
    Traceback (most recent call last):
        ...
    AssertionError: Empty enums are not supported

    ## can't have enum values that look like special function names
    ## since these could collide and lead to non-obvious errors
    >>> x = enum('a','b','c','__cmp__')
    Traceback (most recent call last):
        ...
    AssertionError: Enum values beginning with __ are not supported

LIMITATIONS
    Enum values of unicode type are not preserved, mapped to ASCII instead.

    """
    ## must have at least one enum value
    assert names, 'Empty enums are not supported'
    ## enum values must be strings
    assert len([i for i in names if not isinstance(i, types.StringTypes) and not \
        isinstance(i, unicode)]) == 0, 'Enum values must be string or unicode'
    ## enum values must not collide with special function names
    assert len([i for i in names if i.startswith("__")]) == 0,\
        'Enum values beginning with __ are not supported'
    ## each enum value must be unique from all others
    assert names == uniquify(names), 'Enums must not repeat values'

    class EnumClass(object):
        """ See parent function for explanation """

        __slots__ = names

        def __iter__(self):
            return iter(constants)

        def __len__(self):
            return len(constants)

        def __getitem__(self, i):
            ## this makes xx['name'] possible
            if isinstance(i, types.StringTypes):
                i = names.index(i)
            ## handles the more normal xx[0]
            return constants[i]

        def __repr__(self):
            return 'enum' + str(names)

        def __str__(self):
            return 'enum ' + str(constants)

        def index(self, i):
            return names.index(i)

    class EnumValue(object):
        """ See parent function for explanation """

        __slots__ = ('__value')

        def __init__(self, value):
            self.__value = value

        value = property(lambda self: self.__value)

        enumtype = property(lambda self: enumtype)

        def __hash__(self):
            return hash(self.__value)

        def __cmp__(self, other):
            assert self.enumtype is other.enumtype, 'Only values from the same enum are comparable'
            return cmp(self.value, other.value)

        def __invert__(self):
            return constants[maximum - self.value]

        def __nonzero__(self):
            ## return bool(self.value)
            ## Original code led to bool(x[0])==False, not correct
            return True

        def __repr__(self):
            return str(names[self.value])

    maximum = len(names) - 1
    constants = [None] * len(names)
    for i, each in enumerate(names):
        val = EnumValue(i)
        setattr(EnumClass, each, val)
        constants[i] = val
    constants = tuple(constants)
    enumtype = EnumClass()
    return enumtype

3

Ecco una variante della soluzione di Alec Thomas :

def enum(*args, **kwargs):
    return type('Enum', (), dict((y, x) for x, y in enumerate(args), **kwargs)) 

x = enum('POOH', 'TIGGER', 'EEYORE', 'ROO', 'PIGLET', 'RABBIT', 'OWL')
assert x.POOH == 0
assert x.TIGGER == 1

3

Questa soluzione è un modo semplice per ottenere una classe per l'enumerazione definita come un elenco (non più fastidiose assegnazioni di numeri interi):

enumeration.py:

import new

def create(class_name, names):
    return new.classobj(
        class_name, (object,), dict((y, x) for x, y in enumerate(names))
    )

example.py:

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))

2
Questo è un modo molto antico di creare classi. Perché non semplicemente usare type(class_name, (object,), dict(...))invece?
capolinea

3

Mentre la proposta enum originale, PEP 354 , è stata respinta anni fa, continua a tornare. Una sorta di enum doveva essere aggiunta a 3.2, ma è stata rimandata a 3.3 e poi dimenticata. E ora c'è un PEP 435 destinato all'inclusione in Python 3.4. L'implementazione di riferimento di PEP 435 è flufl.enum.

A partire da aprile 2013, sembra esserci un consenso generale sul fatto che qualcosa dovrebbe essere aggiunto alla biblioteca standard in 3.4, a condizione che le persone possano concordare su cosa dovrebbe essere quel "qualcosa". Questa è la parte difficile. Vedi le discussioni che iniziano qui e qui e una mezza dozzina di altri thread nei primi mesi del 2013.

Nel frattempo, ogni volta che si presenta, una serie di nuovi progetti e implementazioni appaiono su PyPI, ActiveState, ecc., Quindi se non ti piace il design FLUFL, prova una ricerca PyPI .


3

Utilizza il seguente.

TYPE = {'EAN13':   u'EAN-13',
        'CODE39':  u'Code 39',
        'CODE128': u'Code 128',
        'i25':     u'Interleaved 2 of 5',}

>>> TYPE.items()
[('EAN13', u'EAN-13'), ('i25', u'Interleaved 2 of 5'), ('CODE39', u'Code 39'), ('CODE128', u'Code 128')]
>>> TYPE.keys()
['EAN13', 'i25', 'CODE39', 'CODE128']
>>> TYPE.values()
[u'EAN-13', u'Interleaved 2 of 5', u'Code 39', u'Code 128']

L'ho usato per le scelte del modello di Django e sembra molto pitonico. Non è davvero un Enum, ma fa il lavoro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.