enum – Enumeraciones estandarizadas a partir de Python 3.4

Versión: 3.4+

Introducción

Una enumeración es un conjunto de valores constantes representados por un nombre. Generalmente los valores no se repiten, aunque éste no es siempre el caso; y la sintáxis es la siguiente:

enumeración.miembro

En donde enumeración es el nombre de la misma, mientras que miembro es justamente éso, un método de la clase que representa un valor. Por ejemplo:

if respuesta == respuestas.si:
    cargar()
elif respuesta == respuestas.no:
    salir()

En este caso respuestas es la enumeración y si y no son dos de sus miembros. Generalmente los valores representados por cada miembro son numéricos, pero también pueden tratarse de cadenas o cualquier otro valor.

Hasta la versión 3.3 las enumeraciones han dado que hablar, sobretodo para programadores que migraban desde otros lenguajes que las integraban por defecto. De todas maneras, siempre fue posible implementar de algún u otro modo las enumeraciones en versiones anteriores a la 3.4. Tan solo véase esta pregunta en StackOverflow en donde se discute acerca de ello y se presentan gran cantidad de ejemplos y alternativas.

A partir de Python 3.4 el lenguaje implementa enumeraciones de forma nativa, por defecto, vía el módulo enum. En efecto, presenta una extensa documentación que incluye una descripción completa y con ejemplos. Si quieres saber aún más, puedes darle una leída al PEP 435 — Adding an Enum type to the Python standard library, en el que se indican los motivos de la estandarización, descripción, uso, y más.

El módulo enum

Como dije anteriormente, ahora las enumeraciones se encuentran estandarizadas por el módulo enum. Éste exporta tres objetos: Enum, IntEnum y unique. Un ejemplo de una enumeración haciendo uso del módulo es el siguiente:

from enum import Enum

class Color(Enum):
    rojo = 1
    verde = 2
    azul = 3

Las enumeraciones se crean como cualquier otra clase, heredando desde Enum o IntEnum (ver más adelante) lo que permite una rápida y fácil interpretación.

Antes de comenzar con la descripción, voy a citar la nota Nomenclature (Nomenclatura) de la documentación oficial, para que te familiarices con el lenguaje.

  • La clase Color es una enumeración (o enum)
  • Los atributos Color.rojo, Color.verde, etc., son miembros de la enumeración.
  • Los miembros de la enumeración tienen nombres y valores (el nombre de Color.rojo es rojo, el valor de Color.azul es 3, etc.)

Recuerda esto de ahora en más para la compresión de lo siguiente.

Enum

De todos los tres objetos exportados, se trata de la clase base. Podrás crear tus enumeraciones heredando de ésta, como en el ejemplo anterior. Teniendo en cuenta el ejemplo en la introducción, podría definirse de la siguiente manera (asumiendo que ya has importado Enum):

class Respuestas(Enum):
    si = 1
    no = 2

Y accederás a sus miembros utilizando, por ejemplo, Respuestas.si, Respuestas.no, etc.

Si acaso quieres mostrar en pantalla algún miembro de la enumeración, simplemente:

>>> print(Respuestas.si)
Respuestas.si
>>> str(Respuestas.si)
'Respuestas.si'

Estarás pensando, seguramente, por qué al imprimir en pantalla o convertir a una cadena la enumeración no muestra su valor. La respuesta es que los miembros de las enumeraciones no son enteros (int), como tampoco cadenas (str), o números de coma flotante (float), sino objetos que contienen un nombre y un valor.

>>> Respuestas.si.name
'si'
>>> Respuestas.no.name
'no'
>>> Respuestas.si.value
1
>>> Respuestas.no.value
2

Las enumeraciones, como también dije anteriormente, pueden contener otros valores no numéricos. Algunos ejemplos:

class Abecedario(Enum):
    vocales = "aeiou"
    consonantes = "bcdfghjklmnñpqrstvwqyz"
    cantidad_letras = 27

>>> Abecedario.vocales
<Abecedario.vocales: 'aeiou'>
>>> Abecedario.vocales.name
'vocales'
>>> Abecedario.vocales.value
'aeiou'
>>> Abecedario.cantidad_letras
<Abecedario.cantidad_letras: 27>
>>> Abecedario.cantidad_letras.name
'cantidad_letras'
>>> Abecedario.cantidad_letras.value
27

Reglas generales

Además de las comentadas anteriormente, debes conocer las siguientes propiedades de las enumeraciones.

Iteración

Puedes iterar entre los miembros de una enumeración. Por ejemplo:

>>> for miembro in Abecedario
...     print(miembro)
...     print(miembro.name)
...     print(miembro.value)
...
Abecedario.vocales
vocales
aeiou
Abecedario.consonantes
consonantes
bcdfghjklmnñpqrstvwqyz
Abecedario.cantidad_letras
cantidad_letras
27

Acceso

Puedes acceder a un miembro de la enumeración por su nombre, al estilo de los diccionarios. Por ejemplo:

>>> Abecedario["vocales"]
<Abecedario.vocales: 'aeiou'>
>>> miembro = "cantidad_letras"
>>> Abecedario[miembro].value
27

También puedes especificar un valor determinado para obtener el miembro correspondiente:

>>> Abecedario(27)
<Abecedario.cantidad_letras: 27>
>>> Abecedario("aeiou")
<Abecedario.vocales: 'aeiou'>

Miembros y valores

Una enumeración no puede tener dos miembros del mismo nombre, por ejemplo:

>>> class Hardware(Enum):
...     teclado = 1
...     teclado = 2
...
Traceback (most recent call last):
    TypeError: Attempted to reuse key: 'teclado'

Pero en muchas ocasiones es útil tener dos o más miembros con el mismo valor, lo cual sí está permitido:

class Hardware(Enum):
    teclado = 1
    mouse = 2
    raton = 2

Comparaciones

Las enumeraciones preferiblemente deben ser comparadas con la palabra reservada is o, en su término negativo, is not. Por ejemplo:

>>> Hardware.mouse is Hardware.raton
True
>>> Hardware.mouse is Hardware.teclado
False
>>> Hardware.teclado is Hardware.teclado
True

Aunque también pueden utilizarse los operadores == y !=:

>>> Hardware.teclado == Hardware.teclado
True
>>> Hardware.mouse == Hardware.mouse
True
>>> Hardware.raton == Hardware.teclado
False
>>> Hardware.raton != Hardware.mouse
False

No intentes comparar enumeraciones heredadas de únicamente Enum con su respectivo valor, siempre retornará False:

>>> Hardware.raton == 2
False
>>> Hardware.mouse == 2
False
>>> Hardware.teclado == 1
False

IntEnum

IntEnum es una clase que hereda de Enum y, a su vez, de int. De esta manera puedes realizar comparaciones número-valor directamente. Veamos un ejemplo:

>>> Implementaciones.cpython == 1
True
>>> Implementaciones.pypy == 2
True
>>> Implementaciones.stackless < Implementaciones.cpython
False
>>> Implementaciones.pypy > Implementaciones.cpython
True

En este caso, los valores de los miembros sí son números enteros (al momento de realizar comparaciones). Un comportamiento idéntico pudo haberse obtenido delcarando la enumeración de la siguiente manera:

>>> class Implementaciones(int, Enum):
...     cpython = 1
...     pypy = 2
...     stackless = 3
...
>>> Implementaciones.pypy < Implementaciones.stackless
True

Teniendo en cuenta esto, puedes crear tus enumeraciones con el tipo que desees:

>>> class Constantes(float, Enum):
...     pi = 3.14
...     e = 2.17
...
>>> Constantes.pi > Constantes.e
True
>>> Constantes.pi
<Constantes.pi: 3.14>
>>> Constantes.e
<Constantes.e: 2.17>

unique

Puedes utilizar el decorador enum.unique para restringir el uso de valores duplicados:

>>> from enum import Enum, unique
>>>
>>> @unique
... class Hardware(Enum):
...     teclado = 1
...     mouse = 2
...     raton = 2
...
Traceback (most recent call last):
    ValueError: duplicate values found in <enum 'Hardware'>: raton -> mouse

Más

Si aún tienes ganas de más sobre enumeraciones, puedes consultar la documentación.



Deja una respuesta