Risposte:
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 ).
enum34
, fare$ pip install enum34
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'
**named
) nella funzione enum per le versioni precedenti supporta i valori personalizzati:enum("blue", "red", "green", black=0)
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 name
e 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)
Enum
le 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
object()
.
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)
__setattr__(self, name, value)
e forse in __delattr__(self, name)
modo che se scrivi accidentalmente Animals.DOG = CAT
, non ci riuscirà silenziosamente.
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.
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
try-except
blocco?
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)
La migliore soluzione per te dipenderebbe da ciò di cui hai bisogno dal tuo falso enum
.
Enum semplice:
Se hai bisogno del enum
solo 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 range
consente 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.
range
te puoi omettere il primo argomento se è 0
my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2'))))
. Quindi my_enum
può essere utilizzato nella ricerca, ad esempio, my_enum['Item0']
può essere un indice in una sequenza. Potresti voler racchiudere il risultato str.split
in una funzione che genera un'eccezione se ci sono duplicati.
Flag1, Flag2, Flag3 = [2**i for i in range(3)]
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:
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)]
in
parola chiave per cercare membri che siano accurati. Esempio di utilizzo:'Claimed' in Enum(['Unclaimed', 'Claimed'])
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]
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.
Python non ha un equivalente incorporato enum
e altre risposte hanno idee per implementare il tuo (potresti anche essere interessato alla versione superiore del ricettario Python).
Tuttavia, nelle situazioni in cui un enum
sarebbe 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)
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.
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).
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)
enum(names)
per enum(*names)
- allora si potrebbe eliminare la parentesi in più quando si chiama esso.
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
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!
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.
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.
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.
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
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)
.__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 stat
modulo.
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 repr
espressione. 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.is
__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.
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
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!
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'
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'
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())
Ecco un approccio con alcune caratteristiche diverse che trovo preziose:
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
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
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',
))
type(class_name, (object,), dict(...))
invece?
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 .
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.