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


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

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


1146


origen


Respuestas:


Los mensajes se han agregado a Python 3.4 como se describe en PEP 435. También ha sido backported 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, prueba 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 python 2)

  • Usar enum34hacer $ pip install enum34
  • Usar aenumhacer $ 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 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 usado así:

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

El soporte para convertir los valores 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 tus enums en salida. Lanzará KeyError si la asignación inversa no existe. Con el primer ejemplo:

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

2320



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

Yo mismo, 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 Enum la clase base. Esto le proporciona un poco de funcionalidad extra, descrita en el PEP. Por ejemplo, los miembros enum son distintos de los enteros, y están compuestos por 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, use el siguiente atajo:

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

Enum 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

721



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)

298



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 perder memoria y no pueda contar:

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

183



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

Enum simple:

Si necesitas el enum como solo una lista de nombres identificando diferentes artículos, la solución por Mark 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 requiere que los artículos pertenezcan a un envase de algún tipo, luego 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

Enum complejo:

Para largas listas de enum o usos más complicados de enum, estas soluciones no serán suficientes. Puede consultar la receta de Will Ware para Simulando enumeraciones en Python publicado en el Libro de cocina de Python. 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 para enum en Python y por qué fue rechazada.


119



El patrón de enum de tipo seguro que se utilizó en Java pre-JDK 5 tiene una número de ventajas. Al igual que en la respuesta de Alexandru, creas un los campos de clase y nivel de clase son los valores enum; sin embargo, la enumeración los valores son instancias de la clase en lugar de enteros pequeños. Esto tiene la ventaja de que sus valores enum no se comparan inadvertidamente a enteros pequeños, puede controlar cómo se imprimen, agregar arbitraria métodos 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 reciente hilo en python-dev señaló que hay un par de bibliotecas enum en la naturaleza, que incluyen:


76



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

56



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

Sin embargo, en situaciones donde enum sería necesario en C, generalmente termino simplemente usando cadenas simples: debido a la forma en que se implementan los objetos / atributos, (C) Python está optimizado para trabajar muy rápido con cadenas cortas de todos modos, por lo que no habría ningún beneficio en el rendimiento al usar enteros. Para protegerse de errores tipográficos / valores no válidos, puede insertar comprobaciones 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 pierdes el beneficio de autocompletar)


44



Por lo tanto, estoy de acuerdo. No hagamos cumplir la seguridad de tipo en Python, pero me gustaría protegerme de los 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 aleja de la colisión de valores al definir mis enumeraciones.

>>> Animal.Cat
2

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

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

44