Pregunta

Soy principalmente desarrollador de C#, pero actualmente estoy trabajando en un proyecto en Python.

¿Cómo puedo representar el equivalente de un Enum en Python?

¿Fue útil?

Solución

Se han agregado enumeraciones a Python 3.4 como se describe en PEP 435.También ha sido respaldado a 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 y 2.4 en pypi.

Para técnicas Enum más avanzadas, pruebe el biblioteca aenum (2.7, 3.3+, mismo autor que enum34.El código no es perfectamente compatible entre py2 y py3, p.Necesitarás __order__ en pitón 2).

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

Instalación enum (sin números) instalará una versión completamente diferente e incompatible.


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

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

o equivalente:

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

En versiones anteriores, una forma de realizar enumeraciones es:

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

que se usa así:

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

También puedes admitir fácilmente la enumeración automática con algo como esto:

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

y usado así:

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

Se puede agregar soporte para convertir los valores nuevamente a nombres de esta manera:

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)

Esto sobrescribe cualquier cosa con ese nombre, pero es útil para representar sus enumeraciones en la salida.Lanzará KeyError si la asignación inversa no existe.Con el primer ejemplo:

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

Otros consejos

Antes de PEP 435, Python no tenía un equivalente pero podías implementar el tuyo propio.

A mí me gusta mantenerlo simple (he visto algunos ejemplos terriblemente complejos en la red), algo como esto...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

En Python 3.4 (PEP 435), puedes hacer enumeración la clase base.Esto le brinda un poco de funcionalidad adicional, descrita en el PEP.Por ejemplo, los miembros de enum son distintos de los números enteros y están compuestos por un name y un value.

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

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

print(Animal.DOG.value)
# 1

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

Si no desea escribir los valores, utilice el siguiente acceso directo:

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

Enum implementaciones Se pueden convertir en listas y son iterables..El orden de sus miembros es el orden de declaración y no tiene nada que ver con sus valores.Por ejemplo:

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

Aquí hay una implementación:

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

Aquí está su uso:

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

print(Animals.DOG)

Si necesita los valores numéricos, esta es la forma más rápida:

dog, cat, rabbit = range(3)

En Python 3.x también puedes agregar un marcador de posición con estrella al final, que absorberá todos los valores restantes del rango en caso de que no te importe desperdiciar memoria y no puedas contar:

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

La mejor solución para usted dependerá de lo que requiera de su falso enum.

enumeración simple:

Si necesitas el enum como sólo una lista de nombres identificando diferentes elementos, la solución por Marcos Harrison (arriba) es genial:

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

Usando un range También le permite configurar cualquier valor inicial:

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

Además de lo anterior, si también requieres que los artículos pertenezcan a un envase de algún tipo, luego incrustarlos en una clase:

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

Para usar el elemento de enumeración, ahora deberá usar el nombre del contenedor y el nombre del elemento:

stype = Stationery.Pen

Enumeración compleja:

Para listas largas de enum o usos más complicados de enum, estas soluciones no serán suficientes.Puedes consultar la receta de Will Ware para Simulando enumeraciones en Python publicado en el Libro de cocina de Python.Hay una versión en línea disponible. aquí.

Más información:

PEP 354:Enumeraciones en Python tiene los detalles interesantes de una propuesta para enum en Python y por qué fue rechazada.

El patrón de Enum TypeFe que se usó en Java Pre-JDK 5 tiene una serie de ventajas.Al igual que en la respuesta de Alexandru, creas un nivel de clase y clase de clase son los valores de Enum;Sin embargo, los valores de Enum son instancias de la clase en lugar de enteros pequeños.Esto tiene la ventaja de que sus valores de Enum no se comparan inadvertidamente igual a los enteros pequeños, puede controlar cómo están impresos, agregar métodos arbitrarios si eso es útil y hacer afirmaciones utilizando ISInstance:

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

   def __str__(self):
       return self.name

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

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

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

Un reciente hilo en python-dev señaló que hay un par de bibliotecas de enumeración disponibles, que incluyen:

Una clase Enum puede ser de una sola línea.

class Enum(tuple): __getattr__ = tuple.index

Cómo usarlo (búsqueda directa e inversa, claves, valores, elementos, 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 no tiene un equivalente incorporado a enum, y otras respuestas tienen ideas para implementar las suyas propias (también le puede interesar el sobre la versión superior en el libro de cocina de Python).

Sin embargo, en situaciones en las que un enum sería llamado en C, normalmente termino simplemente usando cadenas simples:debido a la forma en que se implementan los objetos/atributos, (C)Python está optimizado para funcionar muy rápido con cadenas cortas de todos modos, por lo que realmente no habría ningún beneficio de rendimiento al usar números enteros.Para protegerse contra errores tipográficos/valores no válidos, puede insertar cheques en lugares seleccionados.

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

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

(Una desventaja en comparación con el uso de una clase es que se pierde el beneficio de autocompletar)

Así que estoy de acuerdo.No impongamos seguridad de tipos en Python, pero me gustaría protegerme de errores tontos.Entonces, ¿qué pensamos sobre esto?

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

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

Me evita la colisión de valores al definir mis enumeraciones.

>>> Animal.Cat
2

Hay otra ventaja útil:búsquedas inversas realmente rápidas:

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

El 10 de mayo de 2013, Guido aceptó aceptar PEP 435 en la biblioteca estándar de Python 3.4.¡Esto significa que Python finalmente tiene soporte integrado para enumeraciones!

Hay un backport disponible para Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 y 2.4.Está en Pypi como enumera34.

Declaración:

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

Representación:

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

Iteración:

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

Acceso programático:

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

Para obtener más información, consulte la propuesta.La documentación oficial probablemente llegará pronto.

Prefiero definir enumeraciones en Python así:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Es más a prueba de errores que usar números enteros, ya que no tiene que preocuparse por garantizar que los números enteros sean únicos (p. ej.si dijeras Perro = 1 y Gato = 1 estarías jodido).

Es más a prueba de errores que usar cadenas, ya que no tienes que preocuparte por errores tipográficos (p. ej.x == "catt" falla silenciosamente, pero x == Animal.Catt es una excepción de tiempo de ejecución).

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

Úselo así:

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

Si solo quieres símbolos únicos y no te importan los valores, reemplaza esta línea:

__metaclass__ = M_add_class_attribs(enumerate(names))

con este:

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

Mmm...Supongo que lo más parecido a una enumeración sería un diccionario, definido así:

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

o

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

Luego, puedes usar el nombre simbólico para las constantes como esta:

mymonth = months['January']

Hay otras opciones, como una lista de tuplas, o una tupla de tuplas, pero el diccionario es la única que le proporciona una forma "simbólica" (cadena constante) de acceder al valor.

Editar:¡También me gusta la respuesta de Alexandru!

Otra implementación muy simple de una enumeración en Python, usando namedtuple:

from collections import namedtuple

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

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

o 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 el método anterior que subclases set, esto permite:

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

Pero tiene más flexibilidad ya que puede tener diferentes claves y valores.Esto permite

MyEnum.FOO < MyEnum.BAR

para actuar como se espera si usa la versión que completa valores numéricos secuenciales.

Lo que 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))

Cómo utilizar:

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

Entonces esto le brinda constantes enteras como state.PUBLISHED y las dos tuplas para usar como opciones en los modelos de Django.

A partir de Python 3.4 habrá soporte oficial para enumeraciones.Puedes encontrar documentación y ejemplos. aquí en la página de documentación de Python 3.4.

Las enumeraciones se crean utilizando la sintaxis de clase, lo que las hace fáciles de leer y escribir.Se describe un método de creación alternativo en API funcional.Para definir una enumeración, subclase Enum de la siguiente manera:

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

Davidg recomienda usar dicts.Yo iría un paso más allá y usaría conjuntos:

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

Ahora puedes probar si un valor coincide con uno de los valores del conjunto de esta manera:

if m in months:

Sin embargo, al igual que dF, normalmente uso constantes de cadena en lugar de enumeraciones.

Este es el mejor que he visto:"Enumeraciones de primera clase en Python"

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

Te proporciona una clase y la clase contiene todas las enumeraciones.Las enumeraciones se pueden comparar entre sí, pero no tienen ningún valor en particular;no puedes usarlos como un valor entero.(Al principio me resistí a esto porque estoy acostumbrado a las enumeraciones de C, que son valores enteros.Pero si no puedes usarlo como un número entero, no puedes usarlo como un número entero por error, así que en general creo que es una victoria). Cada enumeración es un valor único.Puede imprimir enumeraciones, puede iterar sobre ellas, puede probar que un valor de enumeración esté "en" la enumeración.Es bastante completo y elegante.

Editar (cfi):El enlace anterior no es compatible con Python 3.Aquí está mi adaptación de enum.py a 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)

Mantenlo simple:

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

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

Entonces:

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

He tenido ocasión de necesitar una clase Enum, con el fin de decodificar un formato de archivo binario.Las características que quería son una definición de enumeración concisa, la capacidad de crear libremente instancias de la enumeración ya sea por valor entero o por cadena, y una útil reprpresentación.Esto es con lo que terminé:

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

Un ejemplo caprichoso de su uso:

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

  • str(), int() y repr() todos producen el resultado más útil posible, respectivamente, el nombre de la enumeración, su valor entero y una expresión de Python que se evalúa como la enumeración.
  • Los valores enumerados devueltos por el constructor se limitan estrictamente a los valores predefinidos, no a valores de enumeración accidentales.
  • Los valores enumerados son únicos;pueden compararse estrictamente con is

Realmente me gusta la solución de Alec Thomas (http://stackoverflow.com/a/1695250):

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

Tiene un aspecto elegante y limpio, pero es sólo una función que crea una clase con los atributos especificados.

Con una pequeña modificación a la función, podemos hacer que actúe un poco más 'enumy':

NOTA:Creé los siguientes ejemplos tratando de reproducir el comportamiento del nuevo estilo de Pygtk 'enums' (como gtk.messageType.verning)

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

Esto crea una enumeración basada en un tipo específico.Además de otorgar acceso a atributos como la función anterior, se comporta como se esperaría de un Enum con respecto a los tipos.También hereda la clase base.

Por ejemplo, enumeraciones de números enteros:

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

Otra cosa interesante que se puede hacer con este método es personalizar el comportamiento específico anulando los 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)

Si le pones un nombre, es tu problema, pero si no, crear objetos en lugar de valores te permite hacer esto:

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

Cuando utilice otras implementaciones ubicadas aquí (también cuando use instancias con nombre en mi ejemplo), debe asegurarse de nunca intentar comparar objetos de diferentes enumeraciones.Aquí hay un posible error:

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

¡Ay!

El nuevo estándar en Python es PEP 435, por lo que una clase Enum estará disponible en futuras versiones de Python:

>>> from enum import Enum

Sin embargo, para comenzar a usarlo ahora puedes instalar el biblioteca original que motivó al PEP:

$ pip install flufl.enum

Entonces tú Puede usarlo según su guía en línea.:

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

El paquete de enumeración de PyPI proporciona una implementación sólida de enumeraciones.Una respuesta anterior mencionó PEP 354;Esto fue rechazado pero la propuesta fue implementada.http://pypi.python.org/pypi/enum.

El uso es fácil y 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'

La sugerencia de Alexandru de usar constantes de clase para enumeraciones funciona bastante bien.

También me gusta agregar un diccionario para cada conjunto de constantes para buscar una representación de cadena legible por humanos.

Esto tiene dos propósitos:a) proporciona una manera sencilla de imprimir su enumeración yb) el diccionario agrupa lógicamente las constantes para que pueda probar la membresía.

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

Aquí hay un enfoque con algunas características diferentes que considero valiosas:

  • permite la comparación > y < basada en el orden en la enumeración, no en el orden léxico
  • Puede abordar el artículo por nombre, propiedad o índice:xa, x['a'] o x[0]
  • admite operaciones de corte como [:] o [-1]

y más importante evita comparaciones entre enumeraciones de diferentes tipos!

Basado estrechamente en http://code.activestate.com/recipes/413486-first-class-enums-in-python.

Aquí se incluyen muchas pruebas documentales para ilustrar las diferencias con este enfoque.

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

Aquí hay una variante de La solución 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 solución es una forma sencilla de obtener una clase para la enumeración definida como una lista (no más asignaciones de números enteros molestas):

enumeración.py:

import new

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

ejemplo.py:

import enumeration

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

Si bien la propuesta de enumeración original, PEP 354, fue rechazado hace años, sigue apareciendo.Se pretendía agregar algún tipo de enumeración a 3.2, pero se retrasó a 3.3 y luego se olvidó.Y ahora hay un PEP 435 destinado a su inclusión en Python 3.4.La implementación de referencia de PEP 435 es flufl.enum.

En abril de 2013, parece haber un consenso general de que algo debería agregarse a la biblioteca estándar en 3.4, siempre y cuando las personas puedan ponerse de acuerdo sobre qué debería ser ese "algo".Esa es la parte difícil.Ver los hilos que empiezan aquí y aquí, y media docena de otros hilos en los primeros meses de 2013.

Mientras tanto, cada vez que surge esto, aparecen una gran cantidad de nuevos diseños e implementaciones en PyPI, ActiveState, etc., así que si no te gusta el diseño de FLUFL, prueba uno búsqueda PyPI.

Una variante (con soporte para obtener el nombre de un valor de enumeración) para La clara respuesta 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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top