Python Enum
Um pedido da lojinha da Ana passa por poucas situações: pendente, pago, enviado, entregue. Um copo de café tem três tamanhos: pequeno, médio, grande. São listas fechadas de opções, que não mudam enquanto o programa roda. Guardar essas opções como textos soltos ("pago", "enviado") funciona, até alguém digitar "enviadoo" e o programa seguir em frente sem reclamar. A enumeração, ou Enum, dá um nome oficial para cada opção, e o Python passa a conferir.
- O problema dos textos soltos, e como o
Enumresolve. - A criar uma enumeração e a usar os membros (
Status.PAGO). - O
.namee o.valuede cada membro. - A numerar sozinho com
auto(). - A percorrer todas as opções com
for. - A achar o membro pelo valor (
Status(2)) e pelo nome (Status["PAGO"]). - Enumerações com métodos, com
match, e as variaçõesStrEnumeIntEnum. - Os erros reais:
Did you mean,is not a valid Status,KeyError,cannot reassign member.
A ideia: o carimbo em vez da caneta
No balcão da lojinha, a Ana marcava a situação de cada pedido escrevendo à mão: "pago", "enviado". Um dia alguém escreveu "env." e outro escreveu "enviadoo", e ninguém mais sabia quais pedidos tinham saído. A solução foi comprar carimbos: um PENDENTE, um PAGO, um ENVIADO, um ENTREGUE. Com carimbo, não tem como errar a letra, e não aparece uma situação que não existe.
O Enum é a caixa de carimbos. Você define as opções uma vez, cada uma com um nome, e no resto do programa só usa os carimbos.
1. O problema dos textos soltos
status = "pago"
if status == "pago":
print("Pode separar o pedido.")
status = "enviadoo" # erro de digitação
if status == "enviado":
print("O pedido saiu para entrega.")
print("Fim:", status)
$ python3 com_texto.py Pode separar o pedido. Fim: enviadoo
O aviso de entrega nunca apareceu, e o Python não reclamou de nada. Para ele, "enviadoo" é um texto como outro qualquer, só não é igual a "enviado". Num programa grande, um erro assim pode levar dias para ser encontrado.
2. A primeira enumeração
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
pedido = Status.PAGO
print(pedido)
print(repr(pedido))
print(pedido.name)
print(pedido.value)
print(type(pedido))
print(pedido == Status.PAGO)
print(pedido is Status.PAGO)
- from enum import Enum
- Traz a classe
Enumdo móduloenum, que já vem com o Python. - class Status(Enum):
- Uma classe que herda de
Enum(aula Herança). É isso que a transforma numa enumeração. - PENDENTE = 1
- Um membro da enumeração: um "carimbo". À esquerda, o nome; à direita, o valor. Os nomes vão em MAIÚSCULAS, porque são constantes: não mudam nunca.
- Status.PAGO
- Usa o membro: o nome da enumeração, um ponto e o nome do membro.
- .name
- O nome do membro, como texto:
'PAGO'. - .value
- O valor que você deu a ele:
2.
$ python3 primeiro.py Status.PAGO <Status.PAGO: 2> PAGO 2 <enum 'Status'> True True
- O
printmostraStatus.PAGO: de qual enumeração e qual membro. - O
repr(aula Métodos Especiais) mostra também o valor:<Status.PAGO: 2>. - O tipo é
<enum 'Status'>: cada membro é um objeto da enumeraçãoStatus. - Cada membro existe uma vez só no programa inteiro. Por isso tanto
==quantoisfuncionam para comparar (a documentação do Python recomenda ois).
Agora o erro de digitação aparece
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
pedido = Status.ENVIADOO
$ python3 digitacao.py
Traceback (most recent call last):
File "/home/ana/digitacao.py", line 10, in <module>
pedido = Status.ENVIADOO
^^^^^^^^^^^^^^^
AttributeError: type object 'Status' has no attribute 'ENVIADOO'. Did you mean: 'ENVIADO'?
"A classe Status não tem o atributo ENVIADOO. Você quis dizer ENVIADO?" O erro aparece na hora, na linha certa, e o Python 3.14 ainda sugere o nome certo. É o carimbo que não existe: ninguém consegue usá-lo.
Missão: o programa usa Status.ENVIADO, mas a enumeração ainda não tem esse membro (rode para ver o erro). Acrescente ENVIADO, com o valor 3.
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
pedido = Status.ENVIADO
print(pedido.name, pedido.value)
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
pedido = Status.ENVIADO
print(pedido.name, pedido.value)
3. auto(): deixe o Python numerar
Muitas vezes o valor não importa: você só quer que cada membro seja diferente dos outros. Em vez de numerar à mão (e correr o risco de repetir um número), use auto():
from enum import Enum, auto
class Status(Enum):
PENDENTE = auto()
PAGO = auto()
ENVIADO = auto()
ENTREGUE = auto()
print(Status.PENDENTE.value, Status.ENTREGUE.value)
print(repr(Status.ENVIADO))
$ python3 auto.py 1 4 <Status.ENVIADO: 3>
O auto() deu 1, 2, 3 e 4, na ordem. Se amanhã a Ana inserir CANCELADO no meio, a numeração se ajusta sozinha.
4. O valor pode ser qualquer coisa
O valor não precisa ser um número de ordem. Na lojinha, cada tamanho de café tem um preço, e o preço pode ser o próprio valor do membro. E, como a enumeração é uma lista fechada, dá para percorrê-la com for (aula for):
from enum import Enum
class Tamanho(Enum):
PEQUENO = 4.50
MEDIO = 6.00
GRANDE = 7.50
for tamanho in Tamanho:
print(tamanho.name, tamanho.value)
print(list(Tamanho))
print(len(Tamanho))
print(Tamanho.MEDIO in Tamanho)
$ python3 percorrer.py PEQUENO 4.5 MEDIO 6.0 GRANDE 7.5 [<Tamanho.PEQUENO: 4.5>, <Tamanho.MEDIO: 6.0>, <Tamanho.GRANDE: 7.5>] 3 True
- O
forpassa pelos membros na ordem em que foram escritos. list(Tamanho)junta todos numa lista (repare noreprde cada um, com os sinais< >).len(Tamanho)conta os membros: 3.inpergunta se um membro pertence à enumeração.
O nome do membro é MEDIO, sem acento, pelo mesmo costume das variáveis: acentos funcionam em nomes, mas não são o costume.
Missão: monte o cardápio de tamanhos. Troque o print(Tamanho) por um for que mostre, para cada tamanho, uma linha como PEQUENO: R$ 4.50 (use .name, .value e o :.2f da f-string).
from enum import Enum
class Tamanho(Enum):
PEQUENO = 4.50
MEDIO = 6.00
GRANDE = 7.50
print(Tamanho)
from enum import Enum
class Tamanho(Enum):
PEQUENO = 4.50
MEDIO = 6.00
GRANDE = 7.50
for tamanho in Tamanho:
print(f"{tamanho.name}: R$ {tamanho.value:.2f}")
5. Do valor ou do nome para o membro
Os dados nem sempre chegam como membros. Do banco de dados chega o número 2; de um formulário chega o texto "pago". A enumeração converte nos dois sentidos:
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
print(Status(2))
print(Status["ENVIADO"])
texto = "pago"
print(Status[texto.upper()])
- Status(2)
- Com parênteses: procura pelo valor. "Qual membro vale 2?"
- Status["ENVIADO"]
- Com colchetes, como num dicionário: procura pelo nome.
- texto.upper()
- Os nomes estão em maiúsculas; o
.upper()(aula Métodos de Texto) converte o"pago"do formulário em"PAGO".
$ python3 buscar.py Status.PAGO Status.ENVIADO Status.PAGO
E se o valor ou o nome não existir? O Python não inventa um membro: dá erro. Os dois erros estão em Quando dá errado.
6. Membro não é número
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
print(Status.PAGO == 2)
print(Status.PAGO.value == 2)
print(Status.PAGO < Status.ENVIADO)
$ python3 comparar_numero.py
False
True
Traceback (most recent call last):
File "/home/ana/comparar_numero.py", line 12, in <module>
print(Status.PAGO < Status.ENVIADO)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: '<' not supported between instances of 'Status' and 'Status'
Três surpresas comuns:
Status.PAGO == 2éFalse. O membro tem o valor 2, mas não é o número 2. É um carimbo, não um número.- Para comparar com o número, compare o
.value:Status.PAGO.value == 2. - Membros de um
Enumcomum não têm ordem: o<dáTypeError(o mesmo erro da aula Métodos Especiais). Se precisar comparar, compare os valores (Status.PAGO.value < Status.ENVIADO.value) ou use oIntEnum(seção 8).
7. Enumerações com métodos, e com match
Uma enumeração é uma classe, então pode ter métodos. Dentro deles, o self é o membro:
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
ENTREGUE = 4
def descricao(self):
return self.name.capitalize()
def terminou(self):
return self is Status.ENTREGUE
for status in Status:
print(status.descricao(), status.terminou())
$ python3 metodos.py Pendente False Pago False Enviado False Entregue True
O match (aula match e case) combina muito bem com enumerações: um case para cada membro.
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
def aviso(status):
match status:
case Status.PENDENTE:
return "Aguardando o pagamento."
case Status.PAGO:
return "Pagamento recebido, separando o pedido."
case Status.ENVIADO:
return "Saiu para entrega."
for status in Status:
print(aviso(status))
$ python3 com_match.py Aguardando o pagamento. Pagamento recebido, separando o pedido. Saiu para entrega.
No case, escreva o membro com o nome da enumeração na frente (Status.PAGO). O ponto avisa ao match que é um valor a comparar.
8. Formas diferentes: StrEnum e IntEnum
Às vezes você quer que os membros também se comportem como textos ou como números, para conversar com código que só conhece textos e números (um arquivo JSON, um banco de dados). Para isso existem duas variações:
from enum import StrEnum, IntEnum
class Cor(StrEnum):
AZUL = "azul"
VERDE = "verde"
class Prioridade(IntEnum):
BAIXA = 1
ALTA = 2
print(Cor.AZUL == "azul")
print("Camiseta " + Cor.VERDE)
print(Prioridade.ALTA > Prioridade.BAIXA)
print(Prioridade.ALTA + 10)
$ python3 strenum.py True Camiseta verde True 12
Com StrEnum, Cor.AZUL é igual ao texto "azul" e pode ser somado a outros textos. Com IntEnum, os membros comparam com > e fazem contas como números. O preço disso é perder parte da proteção: Prioridade.ALTA + 10 dá 12, uma conta que provavelmente não faz sentido.
O StrEnum existe desde o Python 3.11; Enum, IntEnum e auto() são bem mais antigos. No Python 3.14 do guia (e no 3.13 do Debian 13), tudo desta aula funciona.
| Você precisa… | Use |
|---|---|
| Uma lista fechada de opções, e o valor não importa | Enum com auto() |
| Opções que carregam um dado (o preço do tamanho, o código do banco) | Enum com os valores escritos |
| Opções que precisam ser textos de verdade (gravar em JSON, comparar com texto) | StrEnum |
| Opções que precisam de ordem ou de contas (prioridade 1, 2, 3) | IntEnum |
| Uma lista de valores que muda enquanto o programa roda | Uma lista ou um conjunto comum; o Enum é fixo |
Quando dá errado
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
print(Status(9))
$ python3 valor_errado.py
Traceback (most recent call last):
File "/home/ana/valor_errado.py", line 10, in <module>
print(Status(9))
~~~~~~^^^
File "/usr/lib/python3.14/enum.py", line 708, in __call__
return cls.__new__(cls, value)
~~~~~~~~~~~^^^^^^^^^^^^
File "/usr/lib/python3.14/enum.py", line 1193, in __new__
raise ve_exc
ValueError: 9 is not a valid Status
Linha por linha:
File "/home/ana/valor_errado.py", line 10: no seu arquivo, a linha 10,Status(9). É ela que você vai consertar.- As duas entradas de
/usr/lib/python3.14/enum.pysão o código do próprio móduloenum, por onde a busca passou. Não são o defeito. (No editor do guia, o caminho é o mesmo, mas os números dessas linhas saem um pouco diferentes,line 707eline 1192, porque a versão do Python de lá é outra: 3.14.2.) ValueError: 9 is not a valid Status: "9 não é umStatusválido". Nenhum membro vale 9.
Como consertar: se o número vem de fora (do teclado, de um arquivo), trate o erro com try/except ValueError (aula try e except), como na missão final.
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
print(Status["pago"])
$ python3 nome_errado.py
Traceback (most recent call last):
File "/home/ana/nome_errado.py", line 10, in <module>
print(Status["pago"])
~~~~~~^^^^^^^^
File "/usr/lib/python3.14/enum.py", line 775, in __getitem__
return cls._member_map_[name]
~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'pago'
Com colchetes, a busca é como num dicionário (repare no _member_map_, o dicionário interno de membros), e o erro é o mesmo de uma chave que não existe: KeyError: 'pago'. O nome do membro é PAGO, e maiúsculas contam. Como consertar: Status[texto.upper()].
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
Status.PAGO = 5
$ python3 reatribuir.py
Traceback (most recent call last):
File "/home/ana/reatribuir.py", line 9, in <module>
Status.PAGO = 5
^^^^^^^^^^^
File "/usr/lib/python3.14/enum.py", line 821, in __setattr__
raise AttributeError('cannot reassign member %r' % (name, ))
AttributeError: cannot reassign member 'PAGO'
"Não é possível reatribuir o membro PAGO." Os carimbos são fixos: é justamente para isso que o Enum existe. Se o pedido mudou de situação, mude a variável do pedido (pedido = Status.ENVIADO), não o carimbo.
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
PAGO = 3
$ python3 repetido.py
Traceback (most recent call last):
File "/home/ana/repetido.py", line 4, in <module>
class Status(Enum):
...<2 lines>...
PAGO = 3
File "/home/ana/repetido.py", line 7, in Status
PAGO = 3
^^^^
File "/usr/lib/python3.14/enum.py", line 391, in __setitem__
raise TypeError('%r already defined as %r' % (key, self[key]))
TypeError: 'PAGO' already defined as 2
"PAGO já foi definido como 2." O rastro aponta a linha 7, o segundo PAGO (o ...<2 lines>... é o Python resumindo as linhas da classe que não interessam). Numa classe comum, a segunda linha apagaria a primeira sem aviso; no Enum, cada nome é único. Como consertar: dê outro nome ao membro, ou apague a linha repetida.
import
class Status(Enum):
PENDENTE = 1
$ python3 sem_import.py
Traceback (most recent call last):
File "/home/ana/sem_import.py", line 1, in <module>
class Status(Enum):
^^^^
NameError: name 'Enum' is not defined
Como consertar: from enum import Enum (e auto, StrEnum ou IntEnum, se usar) no começo do arquivo.
Referência rápida
| Escrita | O que faz |
|---|---|
from enum import Enum, auto | Traz a classe Enum e o auto |
class Status(Enum): | Cria uma enumeração |
PAGO = 2 / PAGO = auto() | Um membro, com valor escrito ou automático |
Status.PAGO | Usa um membro |
membro.name / membro.value | O nome ('PAGO') / o valor (2) |
for s in Status: | Percorre os membros, na ordem escrita |
list(Status), len(Status) | Todos os membros numa lista / quantos são |
Status(2) | Acha o membro pelo valor (ou ValueError) |
Status["PAGO"] | Acha o membro pelo nome (ou KeyError) |
pedido is Status.PAGO | Compara (também vale ==) |
StrEnum / IntEnum | Membros que também são textos / números |
| Sintoma | Causa provável |
|---|---|
AttributeError: type object 'Status' has no attribute 'X'. Did you mean: …? | Nome do membro digitado errado |
ValueError: 9 is not a valid Status | Status(9): nenhum membro tem esse valor |
KeyError: 'pago' | Status["pago"]: o nome não existe (maiúsculas contam) |
Status.PAGO == 2 dá False | O membro não é o número: compare .value |
'<' not supported between instances of 'Status' and 'Status' | Enum comum não tem ordem: compare .value ou use IntEnum |
AttributeError: cannot reassign member 'PAGO' | Tentou mudar um membro |
TypeError: 'PAGO' already defined as 2 | Nome repetido na enumeração |
Exercícios
1. O nome e o valor
Com class Tamanho(Enum): PEQUENO = 4.50; MEDIO = 6.00, complete:
Tamanho.MEDIO. # dá 6.0
2. Pelo valor
Na enumeração Status desta aula, qual membro o Python devolve em Status(3)? Escreva como o print mostraria:
3. Qual é a principal vantagem de Status.ENVIADO sobre o texto "enviado"?
4. class Status(Enum): PENDENTE = 1; PAGO = 2. Quanto dá Status.PAGO == 2?
5. O que auto() faz?
6. O texto "enviado" veio de um formulário. Como achar o membro Status.ENVIADO?
Missão final: o pedido pelo número
Missão: o sistema da lojinha guarda a situação do pedido como um número. O programa lê esse número com input() (aqui, o 2). Converta o número no membro da enumeração e mostre a situação (Situação do pedido: PAGO, ou Status.PAGO, se você mostrar o membro inteiro). (O que acontece com um número que não existe, como o 9, está logo abaixo da caixa.)
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
ENTREGUE = 4
numero = int(input("Número da situação: "))
try:
status = numero # troque esta linha: converta o número num Status
print("Situação do pedido:", status)
except ValueError:
print("Não existe situação com o número", numero)
from enum import Enum
class Status(Enum):
PENDENTE = 1
PAGO = 2
ENVIADO = 3
ENTREGUE = 4
numero = int(input("Número da situação: "))
try:
status = Status(numero)
print("Situação do pedido:", status.name)
except ValueError:
print("Não existe situação com o número", numero)
Com a solução, o lab mostrou, digitando 2 e depois 9:
$ python3 main.py Número da situação: 2 Situação do pedido: PAGO $ python3 main.py Número da situação: 9 Não existe situação com o número 9
Resumo
- Um
Enumé uma lista fechada de opções com nome:class Status(Enum):e um membro por linha, em MAIÚSCULAS. - Cada membro tem
.name(o nome, como texto) e.value(o valor). Useauto()quando o valor não importa. - Erro de digitação num membro vira
AttributeErrorna hora (com "Did you mean"), em vez de passar calado como um texto errado. forpercorre os membros na ordem escrita;Status(2)acha pelo valor,Status["PAGO"]pelo nome.- O membro não é o seu valor (
Status.PAGO == 2éFalse) e não tem ordem; para isso existem.value,IntEnumeStrEnum. - Membros não podem ser mudados nem repetidos.