¿Cómo puedo representar un 'Enum' en Python?


1143

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

¿Cómo puedo representar el equivalente de una enumeración en Python?

Respuestas:


2689

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

Para técnicas de Enum más avanzadas, pruebe la biblioteca aenum (2.7, 3.3+, mismo autor que enum34. El código no es perfectamente compatible entre py2 y py3, por ejemplo, necesitará __order__en python 2 ).

  • Para usar enum34, hacer$ pip install enum34
  • Para usar aenum, hacer$ pip install aenum

La 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 lograr 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 puede 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 se usa así:

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

El soporte para convertir los valores de nuevo a nombres se puede agregar 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'

1
No pude entender, ¿por qué pasaron kwargs (** named) en el método enum (* secuencial, ** named)? Por favor explica. Sin kwargs también funcionará. Yo lo revisé.
Seenu S

Sería bueno actualizar la función Python 2 para que sea compatible con la API funcional de
Enthon de

El var kwargs ( **named) en la función enum para versiones anteriores es admitir valores personalizados:enum("blue", "red", "green", black=0)
Éric Araujo

823

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

A mí mismo, me gusta mantenerlo simple (he visto algunos ejemplos horriblemente complejos en la red), algo así ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

En Python 3.4 ( PEP 435 ), puede hacer que Enum sea la clase base. Esto le brinda un poco de funcionalidad adicional, descrita en el PEP. Por ejemplo, los miembros de enumeración son distintos de los enteros, y están compuestos por a namey 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"

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

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

EnumLas implementaciones se pueden convertir a 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

51
No, es una variable de clase.
Georg Schölly

246
Python es dinámico por defecto. No hay una razón válida para hacer cumplir la seguridad en tiempo de compilación en un lenguaje como Python, especialmente cuando no hay ninguno. Y otra cosa ... un buen patrón solo es bueno en el contexto en el que fue creado. Un buen patrón también puede ser reemplazado o completamente inútil, dependiendo de las herramientas que esté utilizando.
Alexandru Nedelcu

20
@Longpoke si tiene 100 valores, entonces definitivamente está haciendo algo mal;) Me gustan los números asociados con mis enumeraciones ... son fáciles de escribir (frente a cadenas), se pueden guardar fácilmente en una base de datos y son compatibles con la enumeración de C / C ++, que facilita el cálculo de referencias.
Alexandru Nedelcu

50
Yo uso esto, con los números reemplazados por object().
Tobu

99
El PEP354 original ya no se rechaza simplemente, sino que ahora se marca como reemplazado. PEP435 agrega un Enum estándar para Python 3.4. Ver python.org/dev/peps/pep-0435
Peter Hansen

323

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)

51
Excelente. Esto se puede mejorar aún más mediante la anulación __setattr__(self, name, value)y tal vez __delattr__(self, name)para que si escribe accidentalmente Animals.DOG = CAT, no tenga éxito en silencio.
Joonas Pulakka

15
@shahjapan: Interesante, pero relativamente lento: se realiza una prueba para cada acceso como Animals.DOG; Además, los valores de las constantes son cadenas, de modo que las comparaciones con estas constantes son más lentas que si, por ejemplo, se permitieran enteros como valores.
Eric O Lebigot

3
@shahjapan: Yo diría que esta solución no es tan legible como las soluciones más cortas de Alexandru o Mark, por ejemplo. Sin embargo, es una solución interesante. :)
Eric O Lebigot

Intenté usar la setattr()función dentro del __init__()método en lugar del __getattr__()método overidding . Supongo que se supone que esto funciona de la misma manera: class Enum (object): 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

8
@ AndréTerra: ¿cómo verifica la membresía establecida en un try-exceptbloque?
bgusach

210

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 puede agregar un marcador de posición con estrella al final, que absorberá todos los valores restantes del rango en caso de que no le importe desperdiciar memoria y no pueda contar:

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

1
¡Pero esto puede tomar más memoria!
MJ

No veo el punto del marcador de posición marcado, dado que Python verificará la cantidad de valores para desempaquetar (por lo que hará el recuento por usted).
Gabriel Devillers

@GabrielDevillers, creo que Python generará una excepción si hay una falta de coincidencia en el número de elementos en la tupla para asignar.
Mark Harrison el

1
De hecho, lo hace en mi prueba (Python2,3), pero eso significa que cualquier error de conteo del programador se detectará en la primera prueba (con un mensaje que da el recuento correcto).
Gabriel Devillers

1
No puedo contar ¿El marcador de posición destacado también puede arreglar mis finanzas?
javadba

131

La mejor solución para usted dependería de lo que requiera de su falsificación enum .

Enumeración simple:

Si necesita enumsolo una lista de nombres que identifican diferentes elementos , la solución de Mark Harrison (arriba) es excelente:

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

El uso de a rangetambién le permite establecer cualquier valor inicial :

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

Además de lo anterior, si también requiere que los elementos pertenezcan a un contenedor de algún tipo, incrústelos en una clase:

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

Para usar el elemento enum, ahora necesitaría usar el nombre del contenedor y el nombre del elemento:

stype = Stationery.Pen

Enumeración compleja:

Para largas listas de enumeración o usos más complicados de enumeración, estas soluciones no serán suficientes. Puede consultar la receta de Will Ware para Simular enumeraciones en Python publicada en Python Cookbook . Una versión en línea de eso está disponible aquí .

Más información:

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


77
con rangeusted puede omitir el primer argumento si es 0
ToonAlfrink

Otra enumeración falsa que se adapta a algunos propósitos es my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Entonces my_enumpuede usarse en la búsqueda, por ejemplo, my_enum['Item0']puede ser un índice en una secuencia. Es posible que desee ajustar el resultado de str.splituna función que arroje una excepción si hay duplicados.
Ana Nimbus

¡Agradable! Para Banderas puedesFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx

Esta es la mejor respuesta
Yuriy Pozniak

78

El patrón de enumeración typesafe que se utilizó en Java pre-JDK 5 tiene varias ventajas. Al igual que en la respuesta de Alexandru, crea una clase y los campos de nivel de clase son los valores de enumeración; sin embargo, los valores de enumeración son instancias de la clase en lugar de enteros pequeños. Esto tiene la ventaja de que sus valores de enumeración no se comparan inadvertidamente iguales a enteros pequeños, puede controlar cómo se imprimen, agregar métodos arbitrarios si eso es útil y hacer afirmaciones usando isinstance:

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

   def __str__(self):
       return self.name

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

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

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

Un hilo reciente en python-dev señaló que hay un par de bibliotecas de enumeraciones en la naturaleza, que incluyen:


16
Creo que este es un enfoque muy malo. Animal.DOG = Animal ("perro") Animal.DOG2 = Animal ("perro") afirma Animal.DOG == Animal.DOG2 falla ...
Confusión

11
@ Confusión Se supone que el usuario no debe llamar al constructor, el hecho de que incluso haya un constructor es un detalle de implementación y debe comunicarse con quien esté usando su código que hacer nuevos valores de enumeración no tiene sentido y que el código existente no "hacer lo correcto". Por supuesto, eso no le impide implementar Animal.from_name ("perro") -> Animal.DOG.
Aaron Maenpaa

13
"la ventaja de que sus valores de enumeración no se comparan inadvertidamente iguales a enteros pequeños" ¿Cuál es la ventaja de esto? ¿Qué hay de malo en comparar tu enumeración con enteros? Especialmente si almacena la enumeración en la base de datos, generalmente desea que se almacene como enteros, por lo que tendrá que compararla con los enteros en algún momento.
ibz

3
@Aaaron Maenpaa. correcto. Sigue siendo una forma rota y demasiado complicada de hacerlo.
aaronasterling

44
@AaronMcSmooth Eso realmente depende de si vienes desde la perspectiva C de "Las enumeraciones son solo nombres para un par de entradas" o el enfoque más orientado a objetos donde los valores de enumeración son objetos reales y tienen métodos (que es cómo las enumeraciones en Java 1.5 son, y para qué iba el patrón de enumeración segura de tipo). Personalmente, no me gustan las declaraciones de cambio, así que me inclino hacia valores de enumeración que son objetos reales.
Aaron Maenpaa

61

Una clase de 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)]

Creo que es la solución más elegante y sencilla. En python 2.4 (sí, antiguo servidor heredado) las tuplas no tienen índice. Resolví reemplazar con la lista.
Massimo

Intenté esto en un cuaderno Jupyter y descubrí que no funcionaría como una definición de una línea, pero que se aceptaría poner la definición de getattr en una segunda línea (con sangría).
user5920660

Esta solución me permite usar la inpalabra clave para buscar miembros que sea ordenada. Ejemplo de uso:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini

51

Entonces, estoy de acuerdo. No apliquemos la 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]

Me gusta esto, pero ¿también podría bloquear los valores para la eficiencia con una tupla? Jugué con él y se me ocurrió una versión que establece los valores propios de los argumentos en init . Es bueno poder declarar Animal = Enum('horse', 'dog', 'cat'). También capturo el ValueError en getattr en el caso de que falte un elemento en self.values; en su lugar, parece mejor generar un AttributeError con la cadena de nombre suministrada. No pude lograr que la metaclase funcionara en Python 2.7 debido a mi conocimiento limitado en esa área, pero mi clase personalizada Enum funciona bien con métodos de instancia directa.
trojjer

49

Python no tiene un equivalente incorporado enum, y otras respuestas tienen ideas para implementar la suya (también puede estar interesado en la versión superior en el libro de cocina de Python).

Sin embargo, en situaciones en las enumque se requeriría una en C, generalmente termino 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 no habría Realmente no hay ningún beneficio de rendimiento al usar enteros. Para evitar 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 pierde el beneficio de autocompletar)


2
Prefiero esta solución Me gusta usar tipos incorporados siempre que sea posible.
Seun Osewa

Esa versión no es realmente exagerada. Solo tiene un montón de código de prueba suministrado
Casebash el

1
En realidad, la versión "correcta" está en los comentarios y es mucho más compleja: la versión principal tiene un error menor.
Casebash el

39

El 10/05/2013, Guido acordó aceptar PEP 435 en la biblioteca estándar Python 3.4. ¡Esto significa que Python finalmente tiene soporte incorporado 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 enum34 .

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 más información, consulte la propuesta . La documentación oficial probablemente seguirá pronto.


33

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 asegurarse de que los números enteros sean únicos (por ejemplo, si dijera Perro = 1 y Gato = 1, estaría jodido).

Es más a prueba de errores que usar cadenas, ya que no tiene que preocuparse por los errores tipográficos (por ejemplo, x == "catt" falla en silencio, pero x == Animal.Catt es una excepción en tiempo de ejecución).


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

Úselo así:

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

Si solo desea símbolos únicos y no le importan los valores, reemplace esta línea:

__metaclass__ = M_add_class_attribs(enumerate(names))

con este:

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

11
En mi humilde opinión, sería más limpio si cambiaras enum(names)a enum(*names), entonces podrías dejar el paréntesis extra cuando lo llames.
Chris Lutz

Me gusta este enfoque. De hecho, lo cambié para establecer el valor del atributo en la misma cadena que el nombre, que tiene la buena propiedad de que Animal.DOG == 'PERRO', por lo que se encadenan automáticamente. (Ayuda inmensamente para imprimir la salida de depuración.)
Ted Mielczarek

23

Desde Python 3.4 habrá soporte oficial para las enumeraciones. Puede 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

El portado posterior ahora también es compatible. Este es el camino a seguir.
srock

22

Hmmm ... 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, puede 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 el único que le proporciona una forma "simbólica" (cadena constante) para acceder al valor.

Editar: ¡Me gusta la respuesta de Alexandru también!


Y, sobre todo, puede iterar fácilmente en un diccionario si necesita acceder a sus valores como si necesita que sus valores de cadena aparezcan como elementos de cuadro combinado. Por lo tanto, use un diccionario como reemplazo de las enumeraciones.
LEMUEL ADANE

22

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

Al igual que el método anterior que las 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 los valores de números secuenciales.


20

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 da constantes enteras como state.PUBLISHED y las dos tuplas para usar como opciones en los modelos de Django.


17

davidg recomienda usar dictados. Iría un paso más allá y usaría conjuntos:

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

Ahora puede probar si un valor coincide con uno de los valores del conjunto de la siguiente manera:

if m in months:

Sin embargo, como dF, generalmente solo uso constantes de cadena en lugar de enumeraciones.


¡Sí! ¡Mucho mejor si heredas el conjunto y proporcionas el método getattr !
shahjapan

17

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

16

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

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

Te da 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 en C, que son valores enteros. Pero si no puede usarlo como un entero, no puede usarlo como un 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 resbaladizo.

Editar (cfi): el enlace anterior no es compatible con Python 3. Aquí está mi puerto 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)

Esta receta se utilizó como base para una PEP, que fue rechazada. python.org/dev/peps/pep-0354 Una extensión que me gusta: los valores de enumeración deben tener una variable miembro que le permita obtener el valor entero interno. No debería ser posible lanzar una enumeración a un entero por error, por lo que el .__int__()método debería generar una excepción para una enumeración; pero debería haber una manera de obtener el valor. Y debería ser posible establecer valores enteros específicos en el momento de la definición de clase, por lo que podría usar una enumeración para cosas como las constantes en el statmódulo.
steveha

14

He tenido la ocasión de necesitar una clase Enum, con el propósito de decodificar un formato de archivo binario. Las características que deseaba eran una definición concisa de enumeración, la capacidad de crear libremente instancias de enumeración mediante un valor entero o una cadena, y una presentación útil repr. Esto es lo que terminé con:

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

Un ejemplo caprichoso de usarlo:

>>> class Citrus(Enum):
...     Lemon = 1
...     Lime = 2
... 
>>> Citrus.Lemon
Citrus.Lemon
>>> 
>>> Citrus(1)
Citrus.Lemon
>>> Citrus(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __new__
KeyError: 5
>>> class Fruit(Citrus):
...     Apple = 3
...     Banana = 4
... 
>>> Fruit.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True

Características clave:

  • str(), int()y repr()todos producen la salida más útil posible, respectivamente, el nombre de la enumeración, su valor entero y una expresión de Python que se evalúa de nuevo en 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 singletons; se pueden comparar estrictamente conis

Realmente me gusta el uso de una superclase con su propia metaclase, para facilitar la definición de enumeraciones. Lo que falta aquí es un método __contains__. Me gustaría poder verificar que una variable dada sea parte de la enumeración, principalmente porque quiero las enumeraciones para valores permitidos de un parámetro de función.
xorsyst

Esta es en realidad una versión ligeramente recortada de la que creé originalmente (que puedes encontrar aquí: enum_strict.py ) v que define un __instancecheck__método. Las clases no son colecciones de instancias, por lo que 1 in Fruites absurdo. Sin embargo, la versión vinculada admite isinstance(1, Fruit)cuál sería más correcto en términos de la noción de clases e instancias.
SingleNegationElimination

Pero olvidando las clases y pensando en términos de enumeraciones, entonces tiene sentido pensar en ellas como una colección. Por ejemplo, podría tener una enumeración de modos de apertura de archivos (MODE.OPEN, MODE.WRITE, etc.). Quiero verificar los parámetros de mi función: si el modo en MODO: se lee mucho mejor que isintance (modo, Modo)
xorsyst

He presentado algo muy similar, que admite más que solo entradas, y está documentado y probado, en GitHub: github.com/hmeine/named_constants
hans_meine

12

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, puede instalar la biblioteca original que motivó el PEP:

$ pip install flufl.enum

Luego 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

10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Si lo nombra, es su problema, pero si no crea objetos en lugar de valores le permite hacer esto:

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

Cuando use otras implementaciones ubicadas aquí (también cuando use instancias con nombre en mi ejemplo) debe estar seguro de que nunca intenta comparar objetos de diferentes enumeraciones. Porque aquí hay una posible trampa:

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


9

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)

Es elegante y de aspecto limpio, pero es solo 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 al intentar reproducir el comportamiento de los nuevos 'enums' de estilo de 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

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

Por ejemplo, enumeraciones enteras:

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

esta idea tipo "base" es ordenada :)
MestreLion

sí, tenga en cuenta que también puede hacer esto con el nuevo Python 3.4 Enum: python.org/dev/peps/pep-0435/#other-derived-enumerations
bj0

7

El paquete enum de PyPI proporciona una implementación robusta de enums. Una respuesta anterior mencionó PEP 354; esto fue rechazado pero la propuesta se implementó 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'

5

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 para humanos.

Esto sirve para dos propósitos: a) proporciona una forma simple 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())

5

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

  • permite> y <comparación basada en orden en enumeración, no en 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 lo más importante, evita las comparaciones entre enumeraciones de diferentes tipos !

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

Muchos documentos incluidos aquí para ilustrar las diferencias de 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

3

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

3

Esta solución es una forma simple de obtener una clase para la enumeración definida como una lista (no más asignaciones enteras molestas):

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

2
Esta es una forma muy antigua de crear clases. ¿Por qué no simplemente usar type(class_name, (object,), dict(...))en su lugar?
término

3

Si bien la propuesta de enumeración original, PEP 354 , fue rechazada hace años, sigue volviendo a aparecer. 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 ser incluido en Python 3.4. La implementación de referencia de PEP 435 es flufl.enum.

A partir de abril de 2013, parece haber un consenso general de que se debe agregar algo a la biblioteca estándar en 3.4, siempre y cuando las personas puedan ponerse de acuerdo sobre lo que ese "algo" debería ser. Esa es la parte difícil. Vea los hilos que comienzan aquí y aquí , y media docena de otros hilos en los primeros meses de 2013.

Mientras tanto, cada vez que surge esto, aparece una gran cantidad de nuevos diseños e implementaciones en PyPI, ActiveState, etc., por lo que si no le gusta el diseño FLUFL, intente una búsqueda de PyPI .


3

Usa lo siguiente.

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

Lo usé para las opciones de modelo de Django , y se ve muy pitónico. No es realmente una enumeración, pero hace el trabajo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.