Enums em Python
Aprenda enums em Python: crie membros Enum, IntEnum, Flag e auto(), adicione métodos, compare com segurança e elimine números mágicos do seu código.
Um enum (abreviação de enumeração) é um conjunto de valores nomeados e constantes agrupados sob um único tipo. Em vez de espalhar inteiros ou strings avulsas como 1, 2, "pending", "active" pelo código, você atribui a cada um um nome descritivo — Status.PENDING, Color.RED — e o Python garante que esse nome sempre mapeará para o mesmo valor.
Este capítulo aborda:
- Por que enums existem e quais problemas eles resolvem
- Como criar um enum com a classe
Enum - Como acessar membros pelo nome e pelo valor
- Como iterar sobre um enum
auto()— deixando o Python atribuir valores automaticamenteIntEnum— enums que se comportam como inteirosFlag— enums de bit-flag combináveis- Como adicionar métodos e propriedades a um enum
- Aliases,
@uniquee_missing_ - Quando usar enums versus outros padrões
Antes de ler este capítulo, certifique-se de que você está familiarizado com classes e objetos em Python e tipos de dados em Python.
Por que Usar Enums?
Considere esta função que processa um status de pedido passado como um inteiro simples:
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")Isso funciona, mas tem problemas reais:
- Números mágicos. O que significa
2isoladamente? Você precisa rastrear até a definição da função. - Sem validação.
handle_order(99)não faz nada silenciosamente — sem erro, sem aviso. - Erros de digitação são invisíveis.
handle_order(2)ehandle_order(20)são ambos Python válido. - Refatoração é arriscada. Se você decidir que
1deve significar outra coisa, precisará encontrar cada1no código.
Enums resolvem tudo isso. A mesma lógica escrita com um enum é autodocumentada, segura e fácil de refatorar:
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 activeA intenção é clara, e o Python impede que handle_order(99) coincida acidentalmente com qualquer ramificação.
Criando um Enum
Importe Enum do módulo enum (parte da biblioteca padrão do Python — sem instalação necessária) e crie uma subclasse:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Cada atributo de nível de classe (RED, GREEN, BLUE) torna-se um membro do enum. Os valores à direita (1, 2, 3) podem ser inteiros, strings ou qualquer outro tipo — a escolha é sua.
Acessando membros
Existem três formas de acessar um membro do 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 membro expõe dois atributos:
print(Color.RED.name) # RED
print(Color.RED.value) # 1Use .name quando precisar de um rótulo legível por humanos (para logging ou exibição), e .value quando precisar passar o valor subjacente para um sistema externo (um banco de dados, uma API).
repr e type
print(repr(Color.RED)) # <Color.RED: 1>
print(type(Color.RED)) # <enum 'Color'>Um membro de enum é uma instância de sua classe enum, não de int ou str.
Iterando Sobre um Enum
Enums são iteráveis. A iteração produz membros na ordem de definição:
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 3Você também pode verificar a pertinência:
print(Color.RED in Color) # TrueIsso torna os enums úteis para preencher listas suspensas, construir tabelas de despacho similares a switch ou criar listas de opções para entrada do usuário.
auto() — Valores Automáticos
Se os valores específicos não importam — você só precisa que cada membro seja distinto — use auto(). O Python atribui inteiros sequenciais começando em 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() é especialmente útil quando o enum crescerá com o tempo e você não quer renumerar membros manualmente.
Comparando Membros de Enum
Use is ou == para comparar membros. Ambos funcionam, mas is é ligeiramente mais rápido porque os membros de enum são singletons — cada nome mapeia exatamente para um 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) # FalseUm membro de Enum simples não é igual ao seu valor bruto:
print(Color.RED == 1) # FalseIsso é intencional. Evita igualdade acidental entre enums diferentes que compartilham o mesmo inteiro:
class Size(Enum):
SMALL = 1
print(Color.RED == Size.SMALL) # False — different typesSe você precisar de comparação baseada em valor (por exemplo, member > 1), use IntEnum (veja abaixo).
IntEnum — Enums que se Comportam como Inteiros
Os membros de IntEnum também são inteiros Python regulares. Isso significa que você pode usar aritmética, operadores de comparação e passá-los onde um int é esperado:
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) # TrueUm caso de uso comum é ordenar uma lista de membros de 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']Quando preferir Enum em vez de IntEnum
A transparência inteira do IntEnum também é sua fraqueza: Priority.HIGH == 3 é True, então um literal 3 digitado errado comparará silenciosamente como igual a Priority.HIGH. Use Enum simples sempre que quiser segurança estrita de tipos, e use IntEnum somente quando precisar genuinamente de aritmética inteira ou tiver que interagir com uma API que trabalha com números brutos.
Flag — Enums de Bit-Flag Combináveis
Flag é projetado para cenários onde múltiplas opções podem estar ativas ao mesmo tempo. Seus membros são potências de dois, e você os combina com o 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 atribui potências de dois sucessivas (1, 2, 4, 8, …) para que a combinação de membros com | nunca produza resultados ambíguos.
Use Flag para sistemas de permissões, alternâncias de funcionalidades e qualquer situação em que você precise de um conjunto compacto de interruptores booleanos.
Adicionando Métodos e Propriedades
Como um enum é uma classe, você pode adicionar métodos e propriedades a ele. Isso mantém a lógica relacionada dentro do tipo, em vez de espalhá-la em cadeias 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 failedVocê também pode dar a um enum um __init__ personalizado para armazenar dados extras por membro. Forneça os 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.7A tupla (mass, radius) torna-se os argumentos do construtor; self.value ainda contém a tupla completa.
Aliases e @unique
Se dois membros compartilham o mesmo valor, o segundo torna-se um alias — ele resolve para o primeiro membro. Aliases não são produzidos durante a iteração:
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>]Aliases são ocasionalmente úteis (por exemplo, um nome legado apontando para um novo), mas também podem mascarar erros de digitação. Aplique o decorador @unique para proibir valores duplicados completamente:
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 é um bom padrão para qualquer enum onde aliasing acidental seria um bug.
Busca Personalizada com _missing_
Por padrão, chamar Color('unknown') levanta um ValueError. Você pode substituir o método de classe _missing_ para lidar com valores não reconhecidos — por exemplo, para fazer uma busca sem distinção de maiúsculas e 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_ recebe o valor que não foi encontrado. Retorne o membro correspondente ou None (o que deixa o Python levantar seu ValueError padrão).
Quando Usar Enums
Enums são a escolha certa quando:
- Uma variável só pode conter um de um conjunto fixo de estados nomeados (status de pedido, verbo HTTP, naipe de carta).
- Você quer prevenir valores inválidos de passarem silenciosamente.
- O mesmo conceito é comparado em vários lugares e você quer uma única fonte de verdade.
- Você precisa iterar sobre todos os valores válidos (preenchendo um formulário, documentando uma API).
Você provavelmente não precisa de um enum quando:
- O conjunto de valores é aberto ou muda em tempo de execução (use um dicionário ou uma tabela de consulta de banco de dados).
- Você só precisa de dois estados —
True/Falsecom um significado booleano claro é mais simples. - Os valores vêm de entrada do usuário que deve ser validada contra um esquema — considere uma biblioteca como Pydantic, que se integra perfeitamente com enums do Python.
Para padrões estreitamente relacionados, veja dataclasses em Python (para dados estruturados com valores padrão) e classes abstratas em Python (para impor contratos de interface em subclasses). Se você precisar de contêineres de constantes nomeadas sem toda a maquinaria de enum, o módulo collections do Python oferece namedtuple como alternativa.