Respostas:
Enums foram adicionadas ao Python 3.4, conforme descrito no PEP 435 . Também foi portado para 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4 no pypi.
Para técnicas Enum mais avançadas, tente a biblioteca aenum (2.7, 3.3+, mesmo autor enum34
. O código não é perfeitamente compatível entre py2 e py3, por exemplo, você precisará __order__
em python 2 ).
enum34
, faça$ pip install enum34
aenum
, faça$ pip install aenum
A instalação enum
(sem números) instalará uma versão completamente diferente e incompatível.
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)
ou equivalente:
class Animal(Enum):
ant = 1
bee = 2
cat = 3
dog = 4
Nas versões anteriores, uma maneira de realizar enumerações é:
def enum(**enums):
return type('Enum', (), enums)
que é usado assim:
>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'
Você também pode facilmente suportar a enumeração automática com algo como isto:
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
e usado assim:
>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1
O suporte para converter os valores novamente em nomes pode ser adicionado desta maneira:
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)
Isso substitui qualquer coisa com esse nome, mas é útil para renderizar suas enumerações na saída. Ele lançará KeyError se o mapeamento reverso não existir. Com o primeiro exemplo:
>>> Numbers.reverse_mapping['three']
'THREE'
**named
) na função enum para versões mais antigas é oferecer suporte a valores personalizados:enum("blue", "red", "green", black=0)
Antes do PEP 435, o Python não tinha um equivalente, mas você poderia implementar o seu próprio.
Eu mesmo, gosto de simplificar (já vi alguns exemplos terrivelmente complexos na rede), algo assim ...
class Animal:
DOG = 1
CAT = 2
x = Animal.DOG
No Python 3.4 ( PEP 435 ), você pode tornar o Enum a classe base. Isso fornece um pouco de funcionalidade extra, descrita no PEP. Por exemplo, os membros enum são distintos dos números inteiros e são compostos de 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 você não deseja digitar os valores, use o seguinte atalho:
class Animal(Enum):
DOG, CAT = range(2)
Enum
implementações podem ser convertidas em listas e são iteráveis . A ordem de seus membros é a ordem de declaração e não tem nada a ver com seus valores. Por exemplo:
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()
.
Aqui está uma implementação:
class Enum(set):
def __getattr__(self, name):
if name in self:
return name
raise AttributeError
Aqui está o seu uso:
Animals = Enum(["DOG", "CAT", "HORSE"])
print(Animals.DOG)
__setattr__(self, name, value)
e, talvez, __delattr__(self, name)
para que, se você escrever acidentalmente Animals.DOG = CAT
, ele não seja silenciosamente bem-sucedido.
Animals.DOG
; Além disso, os valores das constantes são seqüências de caracteres, de modo que as comparações com essas constantes são mais lentas do que se, digamos, números inteiros fossem permitidos como valores.
setattr()
função dentro do __init__()
método em vez do __getattr__()
método de overidding . Eu suponho que isso deve funcionar da mesma maneira: classe Enum (objeto): 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
bloco?
Se você precisar dos valores numéricos, eis a maneira mais rápida:
dog, cat, rabbit = range(3)
No Python 3.x, você também pode adicionar um espaço reservado com estrela no final, que absorverá todos os valores restantes do intervalo, caso você não se importe em desperdiçar memória e não possa contar:
dog, cat, rabbit, horse, *_ = range(100)
A melhor solução para você depende do que você precisa do seu falso enum
.
Enum simples:
Se você precisar da enum
lista apenas de nomes que identificam itens diferentes , a solução de Mark Harrison (acima) é ótima:
Pen, Pencil, Eraser = range(0, 3)
O uso de um range
também permite definir qualquer valor inicial :
Pen, Pencil, Eraser = range(9, 12)
Além do acima, se você também exigir que os itens pertençam a um contêiner de algum tipo, incorpore-os em uma classe:
class Stationery:
Pen, Pencil, Eraser = range(0, 3)
Para usar o item enum, agora você precisa usar o nome do contêiner e o nome do item:
stype = Stationery.Pen
Enum complexo:
Para longas listas de enum ou usos mais complicados de enum, essas soluções não serão suficientes. Você pode consultar a receita de Will Ware para Simulação de enumerações em Python, publicada no Python Cookbook . Uma versão online disso está disponível aqui .
Mais informações:
PEP 354: Enumerações em Python tem os detalhes interessantes de uma proposta de enum em Python e por que ela foi rejeitada.
range
você pode omitir o primeiro argumento se for 0
my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2'))))
. Em seguida, my_enum
pode ser usado na pesquisa, por exemplo, my_enum['Item0']
pode ser um índice em uma sequência. Você pode agrupar o resultado de str.split
uma função que lança uma exceção se houver duplicatas.
Flag1, Flag2, Flag3 = [2**i for i in range(3)]
O padrão de enumeração typesafe usado no Java pré-JDK 5 possui várias vantagens. Assim como na resposta de Alexandru, você cria uma classe e os campos no nível de classe são os valores enum; no entanto, os valores enum são instâncias da classe e não números inteiros pequenos. Isso tem a vantagem de que seus valores de enum não se comparam inadvertidamente com números inteiros pequenos, você pode controlar como eles são impressos, adicionar métodos arbitrários, se isso for útil, e fazer afirmações 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
Um tópico recente no python-dev apontou que existem algumas bibliotecas enum em estado selvagem, incluindo:
Uma classe Enum pode ser uma linha.
class Enum(tuple): __getattr__ = tuple.index
Como usá-lo (pesquisa direta e reversa, chaves, valores, itens etc.)
>>> 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
palavra-chave para procurar membros que sejam legais. Exemplo de uso:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Então eu concordo. Não vamos impor segurança de tipo em Python, mas gostaria de me proteger de erros tolos. Então, o que pensamos sobre isso?
class Animal(object):
values = ['Horse','Dog','Cat']
class __metaclass__(type):
def __getattr__(self, name):
return self.values.index(name)
Isso me impede de colisão de valores ao definir meus enums.
>>> Animal.Cat
2
Há outra vantagem útil: pesquisas reversas muito rápidas:
def name_of(self, i):
return self.values[i]
Animal = Enum('horse', 'dog', 'cat')
. Também pego o ValueError em getattr no caso de um item ausente em self.values - parece melhor gerar um AttributeError com a string de nome fornecida. Não consegui que a metaclasse funcionasse no Python 2.7 com base no meu conhecimento limitado nessa área, mas minha classe Enum personalizada funciona bem com métodos de instância direta.
O Python não possui um equivalente interno enum
e outras respostas têm idéias para implementar o seu próprio (você também pode estar interessado na versão superior do livro de receitas do Python).
No entanto, em situações em que um enum
seria chamado em C, normalmente acabo usando apenas seqüências de caracteres simples : devido à maneira como os objetos / atributos são implementados, (C) o Python é otimizado para trabalhar de maneira muito rápida com sequências curtas, portanto, não realmente não há nenhum benefício em desempenho ao usar números inteiros. Para se proteger contra erros de digitação / valores inválidos, você pode inserir cheques nos locais selecionados.
ANIMALS = ['cat', 'dog', 'python']
def take_for_a_walk(animal):
assert animal in ANIMALS
...
(Uma desvantagem em comparação ao uso de uma classe é que você perde o benefício do preenchimento automático)
Em 10/05/2013, Guido concordou em aceitar o PEP 435 na biblioteca padrão do Python 3.4. Isso significa que o Python finalmente tem suporte embutido para enumerações!
Há um backport disponível para Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4. Está no Pypi como enum34 .
Declaração:
>>> from enum import Enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
Representação:
>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>
Iteração:
>>> for color in Color:
... print(color)
...
Color.red
Color.green
Color.blue
Acesso programático:
>>> Color(1)
Color.red
>>> Color['blue']
Color.blue
Para mais informações, consulte a proposta . A documentação oficial provavelmente seguirá em breve.
Prefiro definir enums em Python da seguinte forma:
class Animal:
class Dog: pass
class Cat: pass
x = Animal.Dog
É mais à prova de erros do que usar números inteiros, já que você não precisa se preocupar em garantir que os números inteiros sejam únicos (por exemplo, se você disse que Dog = 1 e Cat = 1 você seria ferrado).
É mais à prova de erros do que usar strings, pois você não precisa se preocupar com erros de digitação (por exemplo, x == "catt" falha silenciosamente, mas x == Animal.Catt é uma exceção de tempo de execução).
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()
Use-o assim:
Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.DOG = 2 # raises NotImplementedError
se você deseja apenas símbolos únicos e não se importa com os valores, substitua esta linha:
__metaclass__ = M_add_class_attribs(enumerate(names))
com isso:
__metaclass__ = M_add_class_attribs((object(), name) for name in names)
enum(names)
para enum(*names)
- então você pode soltar o parêntese extra ao chamá-lo.
No Python 3.4, haverá suporte oficial para enums. Você pode encontrar documentação e exemplos aqui na página de documentação do Python 3.4 .
As enumerações são criadas usando a sintaxe da classe, o que facilita a leitura e gravação. Um método de criação alternativo é descrito na API Funcional. Para definir uma enumeração, subclasse Enum da seguinte maneira:
from enum import Enum
class Color(Enum):
red = 1
green = 2
blue = 3
Hmmm ... Suponho que a coisa mais próxima de um enum seria um dicionário, definido assim:
months = {
'January': 1,
'February': 2,
...
}
ou
months = dict(
January=1,
February=2,
...
)
Em seguida, você pode usar o nome simbólico para as constantes assim:
mymonth = months['January']
Existem outras opções, como uma lista de tuplas ou uma tupla, mas o dicionário é o único que fornece uma maneira "simbólica" (cadeia constante) de acessar o valor.
Edit: Eu também gosto da resposta de Alexandru!
Outra implementação muito simples de uma enumeração em Python, usando namedtuple
:
from collections import namedtuple
def enum(*keys):
return namedtuple('Enum', keys)(*keys)
MyEnum = enum('FOO', 'BAR', 'BAZ')
ou alternativamente,
# 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())
Como o método acima dessas subclasses set
, isso permite:
'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO
Mas tem mais flexibilidade, pois pode ter chaves e valores diferentes. Isso permite
MyEnum.FOO < MyEnum.BAR
para agir como esperado, se você usar a versão que preenche os valores numéricos seqüenciais.
O que eu 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))
Como usar:
>>> 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'))
Portanto, isso fornece constantes inteiras como state.PUBLISHED e as duas tuplas para serem usadas como opções nos modelos do Django.
A davidg recomenda o uso de dictos. Eu daria um passo adiante e usaria conjuntos:
months = set('January', 'February', ..., 'December')
Agora você pode testar se um valor corresponde a um dos valores do conjunto como este:
if m in months:
como o dF, no entanto, eu normalmente apenas uso constantes em vez de enumerações.
Este é o melhor que eu já vi: "Enums de primeira classe em Python"
http://code.activestate.com/recipes/413486/
Dá a você uma classe, e a classe contém todas as enumerações. As enums podem ser comparadas entre si, mas não têm nenhum valor específico; você não pode usá-los como um valor inteiro. (Resisti a isso no começo porque estou acostumado a enumerações C, que são valores inteiros. Mas se você não pode usá-lo como um número inteiro, não pode usá-lo como um número inteiro por engano, então, no geral, acho que é uma vitória .) Cada enumeração é um valor único. Você pode imprimir enumerações, iterar sobre elas, testar se um valor de enumeração está "na" enumeração. É bem completo e liso.
Editar (cfi): o link acima não é compatível com Python 3. Aqui está o meu porto de enum.py para o 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__()
método deve gerar uma exceção para uma enumeração; mas deve haver uma maneira de obter o valor. E deve ser possível definir valores inteiros específicos no momento da definição da classe, para que você possa usar uma enumeração para coisas como as constantes no stat
módulo.
Eu tive ocasião de precisar de uma classe Enum, com o objetivo de decodificar um formato de arquivo binário. Os recursos que eu queria era a definição concisa de enum, a capacidade de criar livremente instâncias do enum por valor inteiro ou string e uma apresentação útil repr
. Aqui está o que eu acabei com:
>>> 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
...
Um exemplo caprichoso de usá-lo:
>>> 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
Características principais:
str()
, int()
e repr()
todos produzem a saída mais útil possível, respectivamente o nome da enumartion, seu valor inteiro e uma expressão Python que é avaliada novamente na enumeração.is
__instancecheck__
método. Classes não são coleções de instâncias, por isso 1 in Fruit
é um absurdo. No entanto, a versão vinculada suporta o isinstance(1, Fruit)
que seria mais correto em termos de noção de classes e instâncias.
O novo padrão no Python é o PEP 435 , portanto, uma classe Enum estará disponível em versões futuras do Python:
>>> from enum import Enum
No entanto, para começar a usá-lo agora, você pode instalar a biblioteca original que motivou o PEP:
$ pip install flufl.enum
Em seguida, você pode usá-lo conforme seu guia on-line :
>>> 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 você nomear, o problema é seu, mas se não criar objetos em vez de valores, você poderá fazer isso:
>>> DOG = enum('BARK', 'WALK', 'SIT')
>>> CAT = enum('MEOW', 'WALK', 'SIT')
>>> DOG.WALK == CAT.WALK
False
Ao usar outras implementações localizadas aqui (também ao usar instâncias nomeadas no meu exemplo), você deve ter certeza de que nunca tenta comparar objetos de diferentes enumerações. Pois aqui está uma possível armadilha:
>>> 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
Caramba!
Eu realmente gosto da solução de Alec Thomas (http://stackoverflow.com/a/1695250):
def enum(**enums):
'''simple constant "enums"'''
return type('Enum', (object,), enums)
Sua aparência é elegante e limpa, mas é apenas uma função que cria uma classe com os atributos especificados.
Com uma pequena modificação na função, podemos fazê-la agir um pouco mais 'enumy':
NOTA: Criei os seguintes exemplos, tentando reproduzir o comportamento do novo estilo 'enums' do pygtk (como 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
Isso cria uma enumeração baseada em um tipo especificado. Além de fornecer acesso a atributos como a função anterior, ele se comporta como seria de esperar de um Enum em relação aos tipos. Também herda a classe base.
Por exemplo, enumerações inteiras:
>>> 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
Outra coisa interessante que pode ser feita com esse método é personalizar o comportamento específico, substituindo os métodos internos:
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'
O pacote enum do PyPI fornece uma implementação robusta de enumerações. Uma resposta anterior mencionou PEP 354; isso foi rejeitado, mas a proposta foi implementada http://pypi.python.org/pypi/enum .
O uso é fácil e 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'
A sugestão de Alexandru de usar constantes de classe para enumerações funciona muito bem.
Também gosto de adicionar um dicionário para cada conjunto de constantes para pesquisar uma representação de string legível por humanos.
Isso serve para dois propósitos: a) fornece uma maneira simples de imprimir bastante sua enumeração eb) o dicionário agrupa logicamente as constantes para que você possa testar a associação.
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())
Aqui está uma abordagem com algumas características diferentes que considero valiosas:
e o mais importante evita comparações entre enumerações de tipos diferentes !
Baseado em http://code.activestate.com/recipes/413486-first-class-enums-in-python .
Muitos documentos incluídos aqui para ilustrar o que há de diferente nessa abordagem.
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
Aqui está uma variante da solução de 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
Esta solução é uma maneira simples de obter uma classe para a enumeração definida como uma lista (sem mais atribuições inteiras irritantes):
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(...))
lugar?
Embora a proposta original de enum, PEP 354 , tenha sido rejeitada anos atrás, ela continua voltando. Algum tipo de enum deveria ser adicionado ao 3,2, mas foi adiado para 3,3 e depois esquecido. E agora há um PEP 435 destinado à inclusão no Python 3.4. A implementação de referência do PEP 435 é flufl.enum
.
Em abril de 2013, parece haver um consenso geral de que algo deve ser adicionado à biblioteca padrão em 3.4 - desde que as pessoas possam concordar com o que esse "algo" deve ser. Essa é a parte mais difícil. Veja os tópicos iniciando aqui e aqui e meia dúzia de outros tópicos nos primeiros meses de 2013.
Enquanto isso, toda vez que isso acontece, uma série de novos designs e implementações aparecem no PyPI, ActiveState etc., portanto, se você não gosta do design do FLUFL, tente uma pesquisa no PyPI .
Use o seguinte.
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']
Eu usei isso para as opções de modelo do Django , e parece muito pitônico. Não é realmente um Enum, mas faz o trabalho.