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.

Você vai aprender
  • O problema dos textos soltos, e como o Enum resolve.
  • A criar uma enumeração e a usar os membros (Status.PAGO).
  • O .name e o .value de 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ções StrEnum e IntEnum.
  • 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 Enum do módulo enum, 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 print mostra Status.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ção Status.
  • Cada membro existe uma vez só no programa inteiro. Por isso tanto == quanto is funcionam para comparar (a documentação do Python recomenda o is).

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 for passa pelos membros na ordem em que foram escritos.
  • list(Tamanho) junta todos numa lista (repare no repr de cada um, com os sinais < >).
  • len(Tamanho) conta os membros: 3.
  • in pergunta 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 Enum comum 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 o IntEnum (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.

Versão

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 importaEnum 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 rodaUma lista ou um conjunto comum; o Enum é fixo

Quando dá errado

Procurar um valor que não existe
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.py são o código do próprio módulo enum, 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 707 e line 1192, porque a versão do Python de lá é outra: 3.14.2.)
  • ValueError: 9 is not a valid Status: "9 não é um Status vá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.

Procurar um nome em minúsculas
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()].

Tentar mudar um membro
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.

O mesmo nome duas vezes
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.

Esqueceu o 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

EscritaO que faz
from enum import Enum, autoTraz 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.PAGOUsa um membro
membro.name / membro.valueO 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.PAGOCompara (também vale ==)
StrEnum / IntEnumMembros que também são textos / números
SintomaCausa provável
AttributeError: type object 'Status' has no attribute 'X'. Did you mean: …?Nome do membro digitado errado
ValueError: 9 is not a valid StatusStatus(9): nenhum membro tem esse valor
KeyError: 'pago'Status["pago"]: o nome não existe (maiúsculas contam)
Status.PAGO == 2 dá FalseO 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 2Nome 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á 'MEDIO'
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). Use auto() quando o valor não importa.
  • Erro de digitação num membro vira AttributeError na hora (com "Did you mean"), em vez de passar calado como um texto errado.
  • for percorre 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, IntEnum e StrEnum.
  • Membros não podem ser mudados nem repetidos.