Enumeraciones en Python
Aprende enumeraciones en Python: crea Enum, IntEnum, Flag y auto(), agrega métodos, compara de forma segura y elimina números mágicos de tu código.
Un enum (abreviatura de enumeración) es un conjunto de valores con nombre y constantes agrupados bajo un único tipo. En lugar de dispersar enteros o cadenas sin contexto como 1, 2, "pending", "active" por tu código, le das a cada uno un nombre descriptivo — Status.PENDING, Color.RED — y Python garantiza que ese nombre siempre corresponde al mismo valor.
Este capítulo cubre:
- Por qué existen los enums y qué problemas resuelven
- Crear un enum con la clase
Enum - Acceder a miembros por nombre y por valor
- Iterar sobre un enum
auto()— dejar que Python asigne los valores automáticamenteIntEnum— enums que se comportan como enterosFlag— enums de banderas de bits combinables- Agregar métodos y propiedades a un enum
- Alias,
@uniquey_missing_ - Cuándo usar enums frente a otros patrones
Antes de leer este capítulo, asegúrate de estar familiarizado con las clases y objetos de Python y los tipos de datos de Python.
¿Por qué usar enums?
Considera esta función que procesa el estado de un pedido pasado como un entero simple:
def handle_order(status):
if status == 1:
print("Order is pending")
elif status == 2:
print("Order is active")
elif status == 3:
print("Order is complete")Esto funciona, pero tiene problemas reales:
- Números mágicos. ¿Qué significa
2por sí solo? Tienes que rastrear hasta la definición de la función. - Sin validación.
handle_order(99)no hace nada en silencio — sin error, sin advertencia. - Los errores tipográficos son invisibles.
handle_order(2)yhandle_order(20)son Python válido. - La refactorización es arriesgada. Si decides que
1debería significar otra cosa, tienes que encontrar cada1en el código.
Los enums resuelven todo esto. La misma lógica escrita con un enum es autodocumentada, segura y fácil de refactorizar:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
ACTIVE = 2
COMPLETE = 3
def handle_order(status: OrderStatus):
if status == OrderStatus.PENDING:
print("Order is pending")
elif status == OrderStatus.ACTIVE:
print("Order is active")
elif status == OrderStatus.COMPLETE:
print("Order is complete")
handle_order(OrderStatus.ACTIVE) # Order is activeLa intención es clara, y Python evita que handle_order(99) coincida accidentalmente con alguna rama.
Crear un enum
Importa Enum del módulo enum (parte de la biblioteca estándar de Python — no requiere instalación) y crea una subclase:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Cada atributo a nivel de clase (RED, GREEN, BLUE) se convierte en un miembro del enum. Los valores a la derecha (1, 2, 3) pueden ser enteros, cadenas o cualquier otro tipo — la elección es tuya.
Acceder a los miembros
Hay tres formas de acceder a un miembro del enum:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Attribute access (most common)
print(Color.RED) # Color.RED
# By name (square bracket notation)
print(Color['GREEN']) # Color.GREEN
# By value (call the class with the value)
print(Color(3)) # Color.BLUECada miembro expone dos atributos:
print(Color.RED.name) # RED
print(Color.RED.value) # 1Usa .name cuando necesites una etiqueta legible por humanos (para registros o visualización), y .value cuando necesites pasar el valor subyacente a un sistema externo (una base de datos, una API).
repr y type
print(repr(Color.RED)) # <Color.RED: 1>
print(type(Color.RED)) # <enum 'Color'>Un miembro del enum es una instancia de su clase enum, no de int ni de str.
Iterar sobre un enum
Los enums son iterables. La iteración produce los miembros en el orden de definición:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
for color in Color:
print(color.name, color.value)
# RED 1
# GREEN 2
# BLUE 3También puedes verificar la pertenencia:
print(Color.RED in Color) # TrueEsto hace que los enums sean útiles para rellenar listas desplegables, construir tablas de despacho tipo switch o generar listas de opciones para entrada del usuario.
auto() — Valores automáticos
Si los valores específicos no importan — solo te importa que cada miembro sea distinto — usa auto(). Python asigna enteros secuenciales comenzando desde 1:
from enum import Enum, auto
class Direction(Enum):
NORTH = auto()
SOUTH = auto()
EAST = auto()
WEST = auto()
for d in Direction:
print(d.name, d.value)
# NORTH 1
# SOUTH 2
# EAST 3
# WEST 4auto() es especialmente útil cuando el enum crecerá con el tiempo y no quieres renumerar los miembros manualmente.
Comparar miembros de enum
Usa is o == para comparar miembros. Ambos funcionan, pero is es ligeramente más rápido porque los miembros del enum son singletons — cada nombre corresponde exactamente a un objeto:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED is Color.RED) # True
print(Color.RED == Color.RED) # True
print(Color.RED == Color.GREEN) # FalseUn miembro de Enum simple no es igual a su valor bruto:
print(Color.RED == 1) # FalseEsto es intencional. Evita la igualdad accidental entre diferentes enums que comparten el mismo entero:
class Size(Enum):
SMALL = 1
print(Color.RED == Size.SMALL) # False — different typesSi necesitas comparación basada en valores (p. ej., member > 1), usa IntEnum en su lugar (ver más abajo).
IntEnum — Enums que se comportan como enteros
Los miembros de IntEnum son también enteros de Python regulares. Esto significa que puedes usar aritmética, operadores de comparación y pasarlos donde se espera un int:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH > Priority.LOW) # True
print(Priority.MEDIUM + 10) # 12
print(Priority.HIGH == 3) # TrueUn caso de uso común es ordenar una lista de miembros del enum:
from enum import IntEnum
class Level(IntEnum):
LOW = 1
MED = 2
HIGH = 3
levels = [Level.HIGH, Level.LOW, Level.MED]
print([l.name for l in sorted(levels)]) # ['LOW', 'MED', 'HIGH']Cuándo preferir Enum sobre IntEnum
La transparencia entera de IntEnum también es su debilidad: Priority.HIGH == 3 es True, por lo que un literal mal escrito 3 se comparará silenciosamente como igual a Priority.HIGH. Usa Enum simple cuando quieras seguridad de tipos estricta, y usa IntEnum solo cuando genuinamente necesites aritmética entera o debas interactuar con una API que trabaja con números brutos.
Flag — Enums de banderas de bits combinables
Flag está diseñado para escenarios donde múltiples opciones pueden estar activas al mismo tiempo. Sus miembros son potencias de dos y se combinan con el operador | (OR bit a bit):
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
ALL = READ | WRITE | EXECUTE
user = Permission.READ | Permission.WRITE
print(user) # Permission.WRITE|READ
print(Permission.READ in user) # True
print(Permission.EXECUTE in user) # Falseauto() dentro de Flag asigna potencias de dos sucesivas (1, 2, 4, 8, …) de modo que combinar miembros con | nunca produce resultados ambiguos.
Usa Flag para sistemas de permisos, interruptores de funcionalidades y cualquier situación en la que necesites un conjunto compacto de interruptores booleanos.
Agregar métodos y propiedades
Dado que un enum es una clase, puedes agregarle métodos y propiedades. Esto mantiene la lógica relacionada dentro del tipo en lugar de dispersarla en cadenas de if/elif:
from enum import Enum
class HttpStatus(Enum):
OK = 200
CREATED = 201
NOT_FOUND = 404
INTERNAL_ERROR = 500
@property
def is_success(self):
return 200 <= self.value < 300
@property
def is_error(self):
return self.value >= 400
def handle_response(status: HttpStatus):
if status.is_success:
print(f"{status.value} {status.name}: request succeeded")
elif status.is_error:
print(f"{status.value} {status.name}: request failed")
handle_response(HttpStatus.OK) # 200 OK: request succeeded
handle_response(HttpStatus.NOT_FOUND) # 404 NOT_FOUND: request failedTambién puedes darle a un enum un __init__ personalizado para almacenar datos adicionales por miembro. Proporciona los valores como tuplas:
from enum import Enum
class Planet(Enum):
MERCURY = (3.303e+23, 2.4397e6)
VENUS = (4.869e+24, 6.0518e6)
EARTH = (5.976e+24, 6.37814e6)
def __init__(self, mass, radius):
self.mass = mass
self.radius = radius
@property
def surface_gravity(self):
G = 6.67430e-11
return G * self.mass / (self.radius ** 2)
print(round(Planet.EARTH.surface_gravity, 2)) # 9.8
print(round(Planet.MERCURY.surface_gravity, 2)) # 3.7La tupla (mass, radius) se convierte en los argumentos del constructor; self.value sigue conteniendo la tupla completa.
Alias y @unique
Si dos miembros comparten el mismo valor, el segundo se convierte en un alias — se resuelve al primer miembro. Los alias no se producen durante la iteración:
from enum import Enum
class Status(Enum):
ACTIVE = 1
RUNNING = 1 # alias for ACTIVE
print(Status.ACTIVE is Status.RUNNING) # True
print(list(Status)) # [<Status.ACTIVE: 1>]Los alias son ocasionalmente útiles (p. ej., un nombre heredado que apunta a uno nuevo), pero también pueden ocultar errores tipográficos. Aplica el decorador @unique para prohibir valores duplicados por completo:
from enum import Enum, unique
@unique
class Status(Enum):
PENDING = 1
ACTIVE = 2
INACTIVE = 3
# Trying to add a duplicate value to a @unique enum raises ValueError:
# ValueError: duplicate values found in <enum 'Bad'>: B -> A@unique es una buena opción predeterminada para cualquier enum donde el alias accidental sería un error.
Búsqueda personalizada con _missing_
Por defecto, llamar a Color('unknown') lanza un ValueError. Puedes sobreescribir el método de clase _missing_ para manejar valores no reconocidos — por ejemplo, para hacer una búsqueda sin distinción de mayúsculas y minúsculas:
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
@classmethod
def _missing_(cls, value):
if isinstance(value, str):
for member in cls:
if member.value == value.lower():
return member
return None
print(Color('RED')) # Color.RED
print(Color('Green')) # Color.GREEN_missing_ recibe el valor que no se encontró. Devuelve el miembro correspondiente, o None (lo que permite que Python lance su ValueError predeterminado).
Cuándo usar enums
Los enums son la elección correcta cuando:
- Una variable solo puede contener uno de un conjunto fijo de estados con nombre (estado de un pedido, verbo HTTP, palo de carta).
- Quieres prevenir que valores inválidos pasen silenciosamente.
- El mismo concepto se compara en múltiples lugares y quieres una única fuente de verdad.
- Necesitas iterar sobre todos los valores válidos (rellenar un formulario, documentar una API).
Probablemente no necesitas un enum cuando:
- El conjunto de valores es abierto o cambia en tiempo de ejecución (usa un diccionario o una tabla de búsqueda en base de datos).
- Solo necesitas dos estados —
True/Falsecon un significado booleano claro es más simple. - Los valores provienen de entrada del usuario que debe validarse contra un esquema — considera una biblioteca como Pydantic, que se integra perfectamente con los enums de Python.
Para patrones estrechamente relacionados, consulta dataclasses de Python (para datos estructurados con valores predeterminados) y clases abstractas de Python (para imponer contratos de interfaz en subclases). Si necesitas contenedores de constantes con nombre sin toda la maquinaria de enums, el módulo collections de Python ofrece namedtuple como alternativa.