Como posso representar um 'Enum' em Python?


1143

Sou principalmente desenvolvedor de C #, mas atualmente estou trabalhando em um projeto em Python.

Como posso representar o equivalente a um Enum em Python?

Respostas:


2689

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

  • Para usar enum34, faça$ pip install enum34
  • Para usar 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'

1
Não pude entender, por que eles passaram kwargs (** nomeado) no método enum (* sequencial, ** nomeado)? Por favor, explica. Sem kwargs também funcionará. Eu verifiquei.
precisa

Seria bom para atualizar a função Python 2 para ser compatível com API funcional do Python 3 do ENUM (nome, valores)
bscan

O var kwargs ( **named) na função enum para versões mais antigas é oferecer suporte a valores personalizados:enum("blue", "red", "green", black=0)
Éric Araujo

823

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 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 você não deseja digitar os valores, use o seguinte atalho:

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

Enumimplementaçõ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

51
Não, é uma variável de classe.
Georg Schölly 5/03/09

246
Python é dinâmico por padrão. Não há motivo válido para impor a segurança em tempo de compilação em uma linguagem como Python, especialmente quando não existe. E outra coisa ... um bom padrão só é bom no contexto em que foi criado. Um bom padrão também pode ser substituído ou completamente inútil, dependendo das ferramentas que você está usando.
Alexandru Nedelcu

20
@ Longpoke se você tiver 100 valores, então você definitivamente está fazendo algo errado;) Eu gosto de números associados às minhas enumerações ... são fáceis de escrever (vs strings), podem ser facilmente persistidos em um banco de dados e são compatíveis com a enumeração C / C ++, que facilita o empacotamento.
Alexandru Nedelcu

50
Eu uso isso, com os números substituídos por object().
Tobu

9
O PEP354 original não é mais apenas rejeitado, mas agora está marcado como substituído. O PEP435 adiciona um Enum padrão para Python 3.4. Veja python.org/dev/peps/pep-0435
Peter Hansen

323

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)

51
Excelente. Isso pode ser melhorado com a substituição __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.
Joonas Pulakka

15
@shahjapan: Interessante, mas relativamente lento: um teste é feito para cada acesso como 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.
Eric O Lebigot

3
@shahjapan: Eu diria que esta solução não é tão legível quanto as soluções mais curtas de Alexandru ou Mark, por exemplo. É uma solução interessante, no entanto. :)
Eric O Lebigot

Eu tentei usar a 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
Harshith JV 15/11/2012

8
@ AndréTerra: como você verifica a associação em um try-exceptbloco?
bgusach

210

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)

1
Mas isso pode levar mais memória!
MJ

Não vejo o ponto do espaço reservado com estrela, já que o Python verificará o número de valores a serem descompactados (portanto, fará a contagem para você).
Gabriel Devillers

@ GabrielDevillers, acho que o Python gerará uma exceção se houver uma incompatibilidade no número de elementos na tupla a serem atribuídos.
Mark Harrison

1
De fato, ocorre no meu teste (Python2,3), mas isso significa que qualquer erro de contagem do programador será detectado no primeiro teste (com uma mensagem informando a contagem correta).
Gabriel Devillers

1
Eu não posso contar. O marcador de posição com estrela também pode corrigir minhas finanças?
Javadba 31/10/19

131

A melhor solução para você depende do que você precisa do seu falso enum .

Enum simples:

Se você precisar da enumlista 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 rangetambé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.


7
com rangevocê pode omitir o primeiro argumento se for 0
ToonAlfrink

Outro enum falso que serve para alguns propósitos é my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Em seguida, my_enumpode ser usado na pesquisa, por exemplo, my_enum['Item0']pode ser um índice em uma sequência. Você pode agrupar o resultado de str.splituma função que lança uma exceção se houver duplicatas.
Ana Nimbus

Agradável! Para Flags você podeFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx 30/09/19

Esta é a melhor resposta
Yuriy Pozniak

78

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:


16
Eu acho que essa é uma abordagem muito ruim. Animal.DOG = Animal ("cachorro") Animal.DOG2 = Animal ("cachorro") afirma Animal.DOG == Animal.DOG2 falha ...
Confusão

11
@Confusion O usuário não deve chamar o construtor, o fato de que existe mesmo um construtor é um detalhe de implementação e você deve se comunicar com quem estiver usando seu código que criar novos valores de enumeração não faz sentido e que sair do código não fará sentido. "faça a coisa Certa". É claro que isso não impede você de implementar Animal.from_name ("dog") -> Animal.DOG.
Aaron Maenpaa

13
"a vantagem que seus valores de enum não se comparam inadvertidamente com números inteiros pequenos" Qual é a vantagem nisso? O que há de errado em comparar sua enumeração com números inteiros? Especialmente se você armazenar a enumeração no banco de dados, geralmente deseja que ela seja armazenada como números inteiros; portanto, você precisará compará-la com números inteiros em algum momento.
ibz 21/09/10

3
@Aaaron Maenpaa. corrigir. Ainda é uma maneira complicada e complicada de fazer isso.
aaronasterling

4
@AaronMcSmooth Isso realmente depende de você vir da perspectiva C de "Enums são apenas nomes para algumas entradas" ou da abordagem mais orientada a objetos em que valores de enum são objetos reais e têm métodos (como é o caso de enums em Java 1,5 e qual é o padrão de enumeração de tipo seguro). Pessoalmente, não gosto de instruções switch, por isso me inclino para valores enum que são objetos reais.
Aaron Maenpaa

61

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

Eu acho que é a solução mais elegante final mais simples. No python 2.4 (sim, servidor antigo antigo), as tuplas não indexam. Eu resolvi substituir com lista.
Massimo

Eu tentei isso em um notebook Jupyter e descobri que não funcionaria como uma definição de uma linha, mas aceitaria colocar a definição de getattr em uma segunda linha (recuada).
user5920660

Esta solução, deixe-me usar a inpalavra-chave para procurar membros que sejam legais. Exemplo de uso:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini

51

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]

Eu gosto disso, mas você também pode bloquear valores para obter eficiência com uma tupla? Eu brinquei com ele e criei uma versão que define self.values ​​de args no init . É bom poder declarar 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.
trojjer

49

O Python não possui um equivalente interno enume 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 enumseria 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)


2
Eu prefiro esta solução. Eu gosto de usar tipos internos sempre que possível.
Seun Osewa 03/02/09

Essa versão não é realmente exagerada. Ele só tem um monte de código de teste fornecido
Casebash 25/10/2009

1
Na verdade, a versão "correta" está nos comentários e é muito mais complexa - a versão principal possui um bug menor.
Casebash

39

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.


33

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


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

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)

11
IMHO, seria mais limpo se você mudasse enum(names)para enum(*names)- então você pode soltar o parêntese extra ao chamá-lo.
18720 Chris Lutz

Eu gosto dessa abordagem. Na verdade, eu mudei para definir o valor do atributo para a mesma string que o nome, que possui a propriedade nice Animal.DOG == 'DOG', para que eles se estratifiquem automaticamente para você. (Ajuda imensamente para imprimir saída de depuração.)
Ted Mielczarek

23

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

Agora, a portabilidade traseira também é suportada. Este é o caminho a percorrer.
Srock

22

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!


E, acima de tudo, você pode iterar facilmente em um dicionário se precisar acessar seus valores, assim como os valores de string para aparecer como itens da caixa de combinação. Portanto, use um dicionário como substituto para enumerações.
LEMUEL ADANE

22

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.


20

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.


17

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.


sim !, muito melhor se você herdar set e fornecer o método getattr !
Shahjapan

17

Mantenha simples:

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

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

Então:

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

16

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)

Esta receita foi usada como base para um PEP, que foi rejeitado. python.org/dev/peps/pep-0354 Uma extensão que eu gosto: os valores enum devem ter uma variável de membro que permita obter o valor inteiro interno. Não deve ser possível converter uma enumeração para um número inteiro por engano; portanto, o .__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 statmódulo.
steveha

14

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.
  • Valores enumerados retornados pelo construtor são limitados estritamente aos valores predefinidos, sem valores de enumeração acidentais.
  • Valores enumerados são singletons; eles podem ser estritamente comparados comis

Eu realmente gosto do uso de uma superclasse com sua própria metaclasse, para facilitar a definição de enums. O que falta aqui é um método __contains__. Eu gostaria de poder verificar se uma determinada variável faz parte da enumeração - principalmente porque desejo enumerações para valores permitidos de um parâmetro de função.
xorsyst

Esta é realmente uma versão ligeiramente aparada da que eu criei originalmente (que você pode encontrar aqui: enum_strict.py ) v que define um __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.
SingleNegationElimination

Mas, esquecendo as aulas e pensando em termos de enumeração, faz sentido pensar nelas como uma coleção. Por exemplo, eu posso ter uma enumeração de modos de abertura de arquivo (MODE.OPEN, MODE.WRITE, etc). Quero verificar os parâmetros a minha função: se o modo de MODE: lê muito melhor do que isintance (modo, Mode)
xorsyst

Tenho colocar-se algo muito semelhante, que suporta mais do que apenas ints, e é documentado e testado, no GitHub: github.com/hmeine/named_constants
hans_meine

12

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

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


9

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'

este tipo ideia "base" é puro :)
MestreLion

sim, observe que você também pode fazer isso com o novo Python 3.4 Enum: python.org/dev/peps/pep-0435/#other-derived-enumerations
bj0

7

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'

5

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

5

Aqui está uma abordagem com algumas características diferentes que considero valiosas:

  • permite> e <comparação com base na ordem enum, não na ordem lexical
  • pode endereçar item por nome, propriedade ou índice: xa, x ['a'] ou x [0]
  • suporta operações de corte como [:] ou [-1]

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

3

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

3

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',
))

2
Essa é uma maneira muito antiga de criar classes. Por que não simplesmente usar em seu type(class_name, (object,), dict(...))lugar?
terminal

3

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 .


3

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.