Python, ¿para qué sirve el tipo Enum?

En Python 3.4, obtuvimos un Enum lib en la biblioteca estándar: enum . Podemos obtener un backport para enum que funciona con Python 2.4 a 2.7 (e incluso 3.1 a 3.3), enum34 en pypi.

Pero nos las hemos arreglado durante bastante tiempo sin este nuevo módulo, así que, ¿por qué lo tenemos ahora?

Tengo una idea general sobre el propósito de enumeraciones de otros idiomas. En Python, ha sido común usar una clase simple de la siguiente manera y referirse a esto como una enumeración:

 class Colors: blue = 1 green = 2 red = 3 

Esto se puede usar en una API para crear una representación canónica del valor, por ejemplo:

 function_of_color(Colors.green) 

Si esto tiene alguna crítica, es mutable, no se puede iterar (fácilmente), y ¿cómo debemos saber la semántica del entero, 2 ?

Entonces supongo que podría usar algo como un tupla con nombre, ¿cuál sería inmutable?

 >>> Colors = namedtuple('Colors', 'blue green red') >>> colors = Colors('blue', 'green', 'red') >>> colors Colors(blue='blue', green='green', red='red') >>> list(colors) ['blue', 'green', 'red'] >>> len(colors) 3 >>> colors.blue 'blue' >>> colors.index(colors.blue) 0 

La creación de la tupla nombrada es un poco redundante (tenemos que escribir cada nombre dos veces) y, por lo tanto, poco elegante. Obtener el “número” del color también es poco elegante (tenemos que escribir los colors dos veces). La comprobación del valor deberá realizarse con cadenas, que serán un poco menos eficientes.

Así que de vuelta a las enumeraciones.

¿Cuál es el propósito de enums? ¿Qué valor crean para el idioma? ¿Cuándo debo usarlos y cuándo debo evitarlos?

¿Cuál es el propósito de enums? ¿Qué valor crean para el idioma? ¿Cuándo debo usarlos y cuándo debo evitarlos?

El tipo Enum entró en Python a través de PEP 435 . El razonamiento dado es:

Las propiedades de una enumeración son útiles para definir un conjunto de valores constantes inmutables y relacionados que pueden o no tener un significado semántico.

Cuando se usan números y cadenas para este propósito, podrían caracterizarse como “números mágicos” o “cadenas mágicas”. Los números rara vez llevan consigo la semántica y las cadenas se confunden fácilmente (¿mayúsculas? ¿Ortografía? ¿Serpiente o caja de camello?)

Los días de la semana y los grados de las cartas escolares son ejemplos de este tipo de colecciones de valores.

Aquí hay un ejemplo de la documentación :

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

Al igual que la clase básica, esto es mucho más fácil de leer y elegante que el ejemplo del ejemplo, también es inmutable, y tiene otros beneficios como veremos a continuación.

Estrictamente dominante: el tipo de miembro de la enumeración es la enumeración

 >>> type(Color.red)  >>> isinstance(Color.green, Color) True 

Esto le permite definir la funcionalidad de los miembros en la definición Enum. La definición de la funcionalidad en los valores se podría lograr con los otros métodos anteriores, pero sería muy poco elegante.

Mejora: coerción de cuerdas

La representación de la cadena es legible por humanos, mientras que la repr tiene más información:

 >>> print(Color.red) Color.red >>> print(repr(Color.red))  

Me parece que esto es una mejora con respecto a los números mágicos e incluso posiblemente mejor que las cadenas de la tupida nombrada.

Iteración (paridad):

La enumeración admite la iteración (como la tupla nombrada, pero no tanto la clase básica) también:

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

El atributo __members__ es un OrderedDict los nombres de las enumeraciones a sus respectivos objetos de enumeración (similar a la función _asdict() de _asdict() ).

Soportado por pickle (paridad)

Puede serializar y deserializar la enumeración (en caso de que alguien esté preocupado por esto):

 >>> import pickle >>> color.red is pickle.loads(pickle.dumps(color.red)) True 

Mejora: Alias

Esta es una buena característica que la clase básica no tiene, y sería difícil decir que el alias estaba allí en el grupo con namedtuple .

 class Color(Enum): red = 1 green = 2 blue = 3 really_blue = 3 

El alias viene después del nombre canónico, pero ambos son iguales:

 >>> Color.blue is Color.really_blue True 

Si se deben prohibir los alias para evitar colisiones de valor, use el decorador enum.unique (una característica estrictamente dominante).

Estrictamente dominante: las comparaciones hechas con is

La enumeración que se pretende probar con is , que es una comprobación rápida de la identidad de un solo objeto en el proceso.

 >>> Color.red is Color.red True >>> Color.red is Color.blue False >>> Color.red is not Color.blue True 

Las pruebas de igualdad también funcionan, pero las pruebas de identidad son óptimas.

Semántica diferente de otras clases de Python.

Las clases de enumeración tienen diferentes semánticas de los tipos regulares de Python. Los valores del Enum son instancias del Enum, y son singletons en la memoria para esos valores, no hay otro propósito para instanciarlos.

 >>> Color.red is Color('red') 

Es importante tener esto en cuenta, quizás sea un inconveniente, pero comparar en esta dimensión es comparar manzanas con naranjas.

Enums no se supone que sean ordenados

Mientras que la clase Enum sabe en qué orden se crean los miembros, no se asume que los enums estén ordenados. Esta es una característica porque muchas cosas que pueden enumerarse no tienen un orden natural y, por lo tanto, el orden sería arbitrario.

Sin embargo, puede ordenar su enumeración (consulte la siguiente sección).

Subclases

No puede subclasificar un Enum con miembros declarados, pero puede subclasificar un Enum que no declara que los miembros compartan comportamientos (consulte la receta OrderedEnum en los documentos ).

Esta es una característica: tiene poco sentido subclasificar un Enum con miembros, pero nuevamente, la comparación es manzanas y naranjas.

¿Cuándo debo usar enum.Enum ?

Esta es la nueva enumeración canónica en Python. Los colaboradores esperarán que sus enums se comporten como estos enums.

Úselo en cualquier lugar donde tenga una fuente canónica de datos enumerados en su código donde desee que se especifique explícitamente el uso del nombre canónico, en lugar de datos arbitrarios.

Por ejemplo, si en su código desea que los usuarios Color.green que no es "Green" , "green" , 2 o "Greene" , sino Color.green , use el objeto enum.Enum. Es tanto explícito como específico.

Hay muchos ejemplos y recetas en la documentación .

¿Cuándo debo evitarlos?

Deja de enrollarte o deja que la gente adivine sobre números mágicos y cadenas. No los evites. Abrázalos

Sin embargo, si se requiere que los miembros de su enumeración sean enteros por razones históricas, existe el IntEnum del mismo módulo, que tiene el mismo comportamiento, pero también es un entero porque IntEnum una subclase del int interno antes de subclasificar el Enum . De la ayuda de IntEnum :

 class IntEnum(builtins.int, Enum) 

podemos ver que los valores de IntEnum se probarían como una instancia de un int .