Enums em Python
LidoNesta aula, você aprenderá a criar e utilizar enums em Python com o módulo enum. Vamos explorar a classe Enum, como acessar valores e nomes, usar auto() para geração automática, e casos de uso práticos como validação, controle de fluxo e representação de estados.
Enums (enumerações) são um recurso poderoso em Python que permite definir um conjunto fixo de constantes simbólicas. Em vez de usar números mágicos ou strings soltas, um enum fornece uma forma limpa, legível e segura de representar estados, categorias ou opções. Nesta aula, vamos mergulhar no módulo enum, criando nossos próprios enums, entendendo como acessar seus valores e nomes, e usando a função auto() para simplificar a definição. Além disso, veremos casos de uso reais que demonstram como os enums podem melhorar a qualidade do código.
Se você já trabalhou com outras linguagens, pode estar familiarizado com enums. Em Python, eles foram introduzidos na versão 3.4 e desde então se tornaram uma prática recomendada para muitos cenários. Vamos começar com o básico e avançar para tópicos mais avançados, sempre com exemplos práticos.
enum.Enum
A classe Enum é a base para criar enums. Para definir um enum, você cria uma subclasse de Enum e define atributos de classe que serão os membros do enum. Cada membro tem um nome e um valor. O valor pode ser qualquer tipo de dado, mas geralmente é um número inteiro ou uma string. Vamos ver um exemplo simples:
from enum import Enum
class Cor(Enum):
VERMELHO = 1
VERDE = 2
AZUL = 3
print(Cor.VERMELHO) # Cor.VERMELHO
print(Cor.VERMELHO.name) # VERMELHO
print(Cor.VERMELHO.value) # 1No exemplo acima, definimos um enum Cor com três membros. Cada membro é uma instância da classe Cor, e você pode acessá-los pelo nome. Internamente, cada membro tem um atributo .name que é o nome do membro (string) e .value que é o valor associado. Os enums são iteráveis, então você pode percorrer seus membros na ordem em que foram definidos:
for cor in Cor:
print(cor.name, cor.value)
# Saída:
# VERMELHO 1
# VERDE 2
# AZUL 3É importante notar que os enums são imutáveis e seus membros são únicos. Você não pode criar dois membros com o mesmo nome, mas pode ter valores duplicados, o que faz com que eles sejam aliases (veremos depois). Além disso, a comparação de enums é feita por identidade, ou seja, Cor.VERMELHO == Cor.VERMELHO é True, mas Cor.VERMELHO == 1 é False, pois o enum não é comparado diretamente com o valor inteiro. Para obter um membro a partir de um valor, você pode usar a sintaxe Cor(1).
print(Cor(1)) # Cor.VERMELHO
print(Cor(2)) # Cor.VERDE
# Se tentar um valor que não existe, levanta ValueErrorOs enums são muito úteis para representar estados finitos, como status de um pedido, direções cardeais, tipos de usuário, etc. Eles garantem que você não use valores inválidos, pois o Python valida na definição e no acesso.
Valores e nomes
Cada membro de um enum possui dois atributos principais: name e value. O name é o identificador do membro (o nome do atributo na classe), e o value é o valor que você associou. Vamos explorar como acessar e manipular esses atributos.
Você pode acessar o nome e o valor de um membro diretamente, como já vimos. Além disso, é possível obter o nome a partir do valor e vice-versa. Para obter o nome de um valor, você pode usar o construtor da classe: Cor(1).name. Para obter o valor de um nome, basta acessar o membro: Cor.VERMELHO.value. Também é possível iterar sobre os membros e obter seus nomes e valores em forma de tupla:
for cor in Cor:
print(f"Nome: {cor.name}, Valor: {cor.value}")Em alguns casos, você pode querer acessar todos os nomes ou todos os valores. O enum fornece o atributo .__members__, que é um dicionário que mapeia nomes para membros. Isso permite uma introspecção mais avançada:
print(Cor.__members__)
# {'VERMELHO': <Cor.VERMELHO: 1>, 'VERDE': <Cor.VERDE: 2>, 'AZUL': <Cor.AZUL: 3>}Você pode usar __members__ para verificar se um nome existe, por exemplo: "VERMELHO" in Cor.__members__. Outra funcionalidade é a criação de aliases. Se dois membros tiverem o mesmo valor, o segundo se torna um alias para o primeiro. Por exemplo:
class Status(Enum):
ATIVO = 1
INATIVO = 2
ATIVO_2 = 1 # alias para ATIVO
print(Status.ATIVO_2) # Status.ATIVO
print(Status.ATIVO == Status.ATIVO_2) # True
print(list(Status)) # [<Status.ATIVO: 1>, <Status.INATIVO: 2>]Como você pode ver, ATIVO_2 é um alias e não aparece na iteração padrão, pois apenas o primeiro membro com um valor é listado. Isso pode ser útil quando você quer fornecer nomes alternativos para o mesmo valor, mas lembre-se de que isso pode confundir se não for bem documentado.
auto
A função auto() é um atalho que gera automaticamente valores para os membros do enum. Isso evita que você tenha que numerar manualmente, o que pode ser propenso a erros. Quando você usa auto(), o Python atribui valores inteiros sequenciais começando em 1, na ordem em que os membros são definidos.
from enum import Enum, auto
class DiaDaSemana(Enum):
SEGUNDA = auto()
TERCA = auto()
QUARTA = auto()
QUINTA = auto()
SEXTA = auto()
SABADO = auto()
DOMINGO = auto()
for dia in DiaDaSemana:
print(dia.name, dia.value)
# Saída:
# SEGUNDA 1
# TERCA 2
# QUARTA 3
# QUINTA 4
# SEXTA 5
# SABADO 6
# DOMINGO 7O auto() pode ser usado em qualquer tipo de enum, mas é mais comum com enums que não precisam de valores específicos. Você pode combinar auto() com valores explícitos, mas é preciso ter cuidado para não gerar conflitos. O comportamento padrão de auto() é incrementar o valor do último membro definido. Se você misturar, por exemplo:
class Exemplo(Enum):
A = auto()
B = 10
C = auto()
print(Exemplo.A.value) # 1
print(Exemplo.B.value) # 10
print(Exemplo.C.value) # 11No exemplo acima, A recebe 1, B recebe 10 e C recebe 11, porque o auto() após um valor explícito continua a sequência a partir do último valor numérico. Isso pode ser útil, mas pode causar confusão se você não entender o mecanismo. Para maior controle, você pode sobrescrever o método _generate_next_value_ para personalizar a geração de valores. Por exemplo, para gerar valores a partir de 0 ou usar strings:
class AutoZero(Enum):
def _generate_next_value_(name, start, count, last_values):
return count
class Cor(AutoZero):
VERMELHO = auto()
VERDE = auto()
AZUL = auto()
print(Cor.VERMELHO.value) # 0
print(Cor.VERDE.value) # 1
print(Cor.AZUL.value) # 2Essa técnica é avançada, mas mostra como o auto() é flexível. Na prática, na maioria dos casos você não precisa sobrescrever, pois o padrão é suficiente.
Casos de uso
Os enums são extremamente úteis em várias situações. Vamos explorar alguns casos práticos onde eles brilham.
1. Estados de uma máquina de estados: Em sistemas que modelam estados, como pedidos ou processos, os enums garantem que você não use valores inválidos. Por exemplo:
class EstadoPedido(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
ENTREGUE = 4
CANCELADO = 5
def atualizar_estado(pedido, novo_estado):
if not isinstance(novo_estado, EstadoPedido):
raise ValueError("Estado inválido")
pedido.estado = novo_estadoCom o enum, você evita que alguém passe uma string como "Pago" ou um número 2.5, pois o tipo é verificado.
2. Configuração e opções: Em vez de usar strings mágicas para opções, enums tornam o código mais autodocumentado. Por exemplo, em um sistema de log, você pode ter níveis de severidade:
class NivelLog(Enum):
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
CRITICAL = 50
def log(mensagem, nivel=NivelLog.INFO):
print(f"[{nivel.name}] {mensagem}")Isso permite que você use log("Algo aconteceu", NivelLog.WARNING) e seja claro o que está acontecendo.
3. Controle de fluxo: Você pode usar enums em estruturas condicionais para comparar com clareza. Por exemplo:
def acao(tecla):
if tecla == Tecla.CIMA:
mover_cima()
elif tecla == Tecla.BAIXO:
mover_baixo()
elif tecla == Tecla.ESQUERDA:
mover_esquerda()
elif tecla == Tecla.DIREITA:
mover_direita()
else:
print("Tecla não suportada")Comparar com um enum é mais seguro do que comparar com números ou strings, pois o compilador (ou o interpretador) pode verificar se o valor é um membro válido.
4. Representação de escolhas em bancos de dados: Muitas vezes você precisa armazenar um valor de enum em um banco. Você pode armazenar o valor inteiro ou o nome. Usar o nome é mais legível, mas menos eficiente; usar o valor é mais eficiente, mas menos legível. Você pode combinar com um campo de enum do banco. Em Python, você pode converter facilmente:
# Para salvar no banco
valor = Cor.VERMELHO.value # 1
# Para recuperar
cor = Cor(1) # Cor.VERMELHOIsso evita inconsistências.
5. Evitar números mágicos: Em código legado, é comum ver números como 1, 2, 3 para representar opções. Enums substituem isso por nomes significativos, melhorando a legibilidade e a manutenção.
Em resumo, os enums são uma ferramenta essencial para escrever código mais robusto e expressivo. Eles capturam a intenção do desenvolvedor e previnem erros.
Boas práticas e observações finais
Ao usar enums, algumas boas práticas podem ajudar:
- Use nomes em maiúsculas para os membros, seguindo a convenção de constantes.
- Evite criar aliases a menos que seja realmente necessário, pois podem causar confusão.
- Prefira usar
auto()quando os valores não têm significado intrínseco. - Se você precisar de valores específicos (como códigos HTTP), defina-os explicitamente.
- Use enums para representar um conjunto fechado de opções, não para listas que podem crescer.
- Lembre-se de que enums são classes, então você pode adicionar métodos a eles.
Por exemplo, você pode adicionar um método para obter uma descrição amigável:
class Cor(Enum):
VERMELHO = 1
VERDE = 2
AZUL = 3
def descricao(self):
return f"A cor {self.name} tem valor {self.value}"
print(Cor.VERMELHO.descricao()) # A cor VERMELHO tem valor 1Isso torna o enum mais poderoso e coeso.
Outra observação: em Python, enums são classes, então você pode usar herança e mixins, mas isso é um tópico avançado. Para a maioria dos casos, os exemplos acima são suficientes.
Exercícios
- Crie um enum chamado
NivelAcessocom os níveisVISITANTE,USUARIO,MODERADOReADMIN, usando valores inteiros 1, 2, 3 e 4 respectivamente. Depois, escreva uma função que receba um nível e imprima uma mensagem de boas-vindas com o nome do nível. - Dado o enum
Corcom os membrosVERMELHO,VERDE,AZULcom valores 1, 2, 3, escreva um código que, dado um valor inteiro, retorne o nome da cor correspondente. Se o valor não existir, retorne "Cor desconhecida". - Usando
auto(), crie um enum chamadoDiaSemanacom os dias de segunda a domingo. Depois, escreva uma função que, dado um dia, retorne se é dia útil (segunda a sexta) ou fim de semana (sábado e domingo). - Considere o enum a seguir. Ele possui um alias. Escreva um código que liste todos os membros únicos (sem aliases) e seus valores.
class Status(Enum): PENDENTE = 1 APROVADO = 2 REJEITADO = 3 APROVADO_FINAL = 2 # alias - Crie um enum chamado
FormatoArquivocom os membrosJPG,PNG,PDF,ZIP. Adicione um métodoextensao()que retorne a extensão correspondente (por exemplo,JPG.extensao()retorna ".jpg"). Em seguida, escreva um código que, dado um nome de arquivo, use o enum para verificar se o formato é suportado (ex.: "foto.jpg" é suportado, "foto.gif" não).
from enum import Enum
class NivelAcesso(Enum):
VISITANTE = 1
USUARIO = 2
MODERADOR = 3
ADMIN = 4
def boas_vindas(nivel):
if nivel == NivelAcesso.VISITANTE:
print("Bem-vindo, visitante!")
elif nivel == NivelAcesso.USUARIO:
print("Bem-vindo, usuário!")
elif nivel == NivelAcesso.MODERADOR:
print("Bem-vindo, moderador!")
elif nivel == NivelAcesso.ADMIN:
print("Bem-vindo, administrador!")
else:
print("Nível desconhecido")
# Teste
boas_vindas(NivelAcesso.ADMIN) # Bem-vindo, administrador!from enum import Enum
class Cor(Enum):
VERMELHO = 1
VERDE = 2
AZUL = 3
def nome_cor(valor):
try:
return Cor(valor).name
except ValueError:
return "Cor desconhecida"
# Teste
print(nome_cor(2)) # VERDE
print(nome_cor(10)) # Cor desconhecidafrom enum import Enum, auto
class DiaSemana(Enum):
SEGUNDA = auto()
TERCA = auto()
QUARTA = auto()
QUINTA = auto()
SEXTA = auto()
SABADO = auto()
DOMINGO = auto()
def tipo_dia(dia):
if dia in (DiaSemana.SABADO, DiaSemana.DOMINGO):
return "Fim de semana"
else:
return "Dia útil"
# Teste
print(tipo_dia(DiaSemana.SEXTA)) # Dia útil
print(tipo_dia(DiaSemana.DOMINGO)) # Fim de semanafor status in Status:
print(f"{status.name} = {status.value}")
# Saída:
# PENDENTE = 1
# APROVADO = 2
# REJEITADO = 3from enum import Enum
import os
class FormatoArquivo(Enum):
JPG = 1
PNG = 2
PDF = 3
ZIP = 4
def extensao(self):
return "." + self.name.lower()
def suportado(nome_arquivo):
ext = os.path.splitext(nome_arquivo)[1].lower()
for formato in FormatoArquivo:
if formato.extensao() == ext:
return True
return False
# Teste
print(suportado("foto.jpg")) # True
print(suportado("foto.gif")) # False