Pergunta

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

Como posso representar o equivalente a um Enum em Python?

Foi útil?

Solução

Enums foram adicionados ao Python 3.4 conforme descrito em PEP 435.Também tem sido portado para 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4 em pypi.

Para técnicas Enum mais avançadas, tente o biblioteca aenum (2.7, 3.3+, mesmo autor de enum34.O código não é perfeitamente compatível entre py2 e py3, por ex.você precisará __order__ em python 2).

  • Usar enum34, fazer $ pip install enum34
  • Usar aenum, fazer $ pip install aenum

Instalando 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 enums é:

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 oferecer suporte facilmente à enumeração automática com algo assim:

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 de volta em nomes pode ser adicionado desta forma:

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.Irá lançar KeyError se o mapeamento reverso não existir.Com o primeiro exemplo:

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

Outras dicas

Antes do PEP 435, o Python não tinha um equivalente, mas você poderia implementar o seu próprio.

Eu mesmo gosto de manter as coisas simples (já vi alguns exemplos terrivelmente complexos na rede), algo assim ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Em Python 3.4 (PEP 435), você pode fazer Enum a classe base.Isso oferece um pouco de funcionalidade extra, descrita no PEP.Por exemplo, membros enum são distintos de inteiros e são compostos por um name e um 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 quiser digitar os valores, use o seguinte atalho:

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

Enum implementações podem ser convertidos em listas e são iteráveis.A ordem dos seus membros é a ordem da declaração e não tem nada a ver com os 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

Aqui está uma implementação:

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

Aqui está seu uso:

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

print(Animals.DOG)

Se você precisar dos valores numéricos, esta é 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 consiga contar:

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

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

Enumeração simples:

Se você precisar do enum como apenas uma lista de nomes identificando diferentes Unid, a solução por Marcos Harrison (acima) é ótimo:

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

Usando um range também permite que você defina qualquer valor inicial:

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

Além do acima exposto, se você também exigir que os itens pertençam a um recipiente de algum tipo, então incorpore-os em uma classe:

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

Para usar o item enum, agora você precisaria 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 Simulando Enumerações em Python publicado no Livro de receitas Python.Uma versão online disso está disponível aqui.

Mais informações:

PEP 354:Enumerações em Python tem detalhes interessantes de uma proposta de enum em Python e por que ela foi rejeitada.

O padrão de enumeração do TypeAfe que foi usado no Java pré-JDK 5 tem várias vantagens.Assim como na resposta de Alexandru, você cria uma classe e campos de nível de classe são os valores da enumeração;No entanto, os valores da enumeração são instâncias da classe e não pequenos números inteiros.Isso tem a vantagem de que seus valores de enum não se comparam inadvertidamente iguais a pequenos números inteiros, você pode controlar como eles são impressos, adicionar métodos arbitrários se isso for útil e fazer afirmações usando o 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 recente tópico em python-dev apontou que existem algumas bibliotecas enum disponíveis, incluindo:

Uma classe Enum pode ser de 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)]

Python não possui um equivalente integrado para enum, e outras respostas têm ideias para implementar as suas próprias (você também pode estar interessado no versão superior no livro de receitas do Python).

Contudo, em situações em que um enum seria chamado em C, geralmente acabo apenas usando strings simples:devido à maneira como os objetos/atributos são implementados, o (C)Python é otimizado para funcionar muito rápido com strings curtas, portanto, não haveria realmente nenhum benefício de desempenho no uso de 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)

Então, eu concordo.Não vamos impor segurança de tipo em Python, mas gostaria de me proteger de erros bobos.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 minhas enumerações.

>>> Animal.Cat
2

Há outra vantagem útil:pesquisas reversas muito rápidas:

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

Em 10/05/2013, Guido concordou em aceitar PEP 435 na biblioteca padrão do Python 3.4.Isso significa que o Python finalmente possui suporte integrado 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 obter mais informações, consulte a proposta.A documentação oficial provavelmente virá em breve.

Prefiro definir enums em Python assim:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

É mais à prova de erros do que usar números inteiros, pois você não precisa se preocupar em garantir que os números inteiros sejam únicos (por exemplo,se você dissesse Cachorro = 1 e Gato = 1 você estaria 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 assim:

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

se você deseja apenas símbolos exclusivos 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)

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

Então, 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 de tuplas, mas o dicionário é o único que fornece uma maneira "simbólica" (string constante) de acessar o valor.

Editar:Também gosto da resposta de Alexandru!

Outra implementação muito simples de um enum 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 que 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 conforme o esperado se você usar a versão que preenche valores de números sequenciais.

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 usar como opções nos modelos Django.

A partir do 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 a gravação.Um método de criação alternativo é descrito na API funcional.Para definir uma enumeração, subclasse Enum da seguinte forma:

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

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

if m in months:

como dF, porém, normalmente uso apenas constantes de string no lugar de enums.

Este é o melhor que 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 enumerações podem ser comparadas entre si, mas não possuem nenhum valor específico;você não pode usá-los como um valor inteiro.(Resisti a isso no início porque estou acostumado com enums 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 enum é um valor único.Você pode imprimir enums, iterar sobre eles, testar se um valor de enum está "dentro" do enum.É bem completo e elegante.

Editar (cfi):O link acima não é compatível com Python 3.Aqui está minha porta de enum.py para 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)

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

Tive a oportunidade de precisar de uma classe Enum, com o propósito de decodificar um formato de arquivo binário.Os recursos que eu queria são uma definição concisa de enum, a capacidade de criar livremente instâncias do enum por valor inteiro ou string e um recurso útil reprrepresentação.Aqui está o que acabei:

>>> 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 extravagante 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 enumeração, seu valor inteiro e uma expressão Python que retorna à enumeração.
  • Os valores enumerados retornados pelo construtor são limitados estritamente aos valores predefinidos, sem valores enum acidentais.
  • Os valores enumerados são singletons;eles podem ser estritamente comparados com is

Gosto muito da solução de Alec Thomas (http://stackoverflow.com/a/1695250):

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

É elegante e limpo, mas é apenas uma função que cria uma classe com os atributos especificados.

Com uma pequena modificação na função, podemos fazer com que ela atue um pouco mais como 'enumy':

OBSERVAÇÃO:Criei os seguintes exemplos tentando reproduzir o comportamento do novo estilo 'enum' 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, ela se comporta como seria de esperar de um Enum em relação aos tipos.Ele também herda a classe base.

Por exemplo, enums inteiros:

>>> 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 este método é personalizar um comportamento específico substituindo métodos integrados:

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'
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 permite que você faça 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 em meu exemplo), você deve ter certeza de nunca tentar comparar objetos de enumerações diferentes.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!

O novo padrão em Python é 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 o biblioteca original que motivou o PEP:

$ pip install flufl.enum

Então você pode usá-lo de acordo com seu guia 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

O pacote enum de PyPI fornece uma implementação robusta de enums.Uma resposta anterior mencionou PEP 354;isso foi rejeitado, mas a proposta foi implementadahttp://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 enums funciona muito bem.

Também gosto de adicionar um dicionário para cada conjunto de constantes para procurar uma representação de string legível por humanos.

Isso serve a dois propósitos:a) fornece uma maneira simples de imprimir 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:

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

e o mais importante evita comparações entre enums de tipos diferentes!

Baseado de perto em http://code.activestate.com/recipes/413486-first-class-enums-in-python.

Muitos doctests 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 A 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 (não há mais atribuições de números inteiros irritantes):

enumeração.py:

import new

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

exemplo.py:

import enumeration

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

Embora a proposta enum original, PEP 354, foi rejeitado anos atrás, continua voltando.Algum tipo de enum deveria ser adicionado ao 3.2, mas foi adiado para o 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 no 3.4 - desde que as pessoas concordem sobre o que esse "algo" deveria ser.Essa é a parte difícil.Veja os tópicos começando aqui e aqui, e meia dúzia de outros tópicos nos primeiros meses de 2013.

Enquanto isso, toda vez que isso surge, uma série de novos designs e implementações aparecem em PyPI, ActiveState, etc., então se você não gosta do design FLUFL, tente um Pesquisa PyPI.

Uma variante (com suporte para obter o nome de um valor enum) para A resposta legal de Alec Thomas:

class EnumBase(type):
    def __init__(self, name, base, fields):
        super(EnumBase, self).__init__(name, base, fields)
        self.__mapping = dict((v, k) for k, v in fields.iteritems())
    def __getitem__(self, val):
        return self.__mapping[val]

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

Numbers = enum(ONE=1, TWO=2, THREE='three')
print Numbers.TWO
print Numbers[Numbers.ONE]
print Numbers[2]
print Numbers['three']
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top