---
title: "Enum em Python: Enum, StrEnum, IntEnum, Flag e auto"
url: "https://python.dev.br/blog/python-enum-strenum-flag/"
markdown_url: "https://python.dev.br/blog/python-enum-strenum-flag.MD"
description: "Guia prático de enum em Python: Enum, StrEnum (3.11), IntEnum, Flag, auto e boas práticas com dataclasses, Pydantic e FastAPI em português brasileiro."
date: "2026-07-22"
author: "Equipe Python Brasil"
---

# Enum em Python: Enum, StrEnum, IntEnum, Flag e auto

Guia prático de enum em Python: Enum, StrEnum (3.11), IntEnum, Flag, auto e boas práticas com dataclasses, Pydantic e FastAPI em português brasileiro.


O módulo `enum` da biblioteca padrão do Python resolve um problema que aparece em todo sistema real: **valores finitos com significado de negócio** — status de pedido, tipo de documento, permissão, canal de notificação. Em vez de espalhar `"pendente"`, `"pago"` e `"cancelado"` como strings mágicas (ou `1`, `2`, `3` como inteiros opacos), você declara um tipo fechado, legível em stack traces e seguro para o type checker.

Este guia cobre `Enum`, `StrEnum` (Python 3.11+), `IntEnum`, `Flag`/`IntFlag`, `auto()`, iteração, comparação, serialização e o encaixe com [dataclasses](/blog/dataclasses-python-guia-completo/), [Pydantic](/blog/pydantic-validacao-dados-python/), [FastAPI](/blog/apis-rest-com-fastapi/) e [pattern matching](/blog/pattern-matching-python-match-case/). Complementa [tipagem estática com mypy](/blog/tipagem-estatica-python-mypy/), [boas práticas Python 2026](/blog/boas-praticas-python-2026/) e o trio funcional da stdlib ([collections](/blog/collections-python-guia-completo/), [itertools](/blog/python-itertools-iteracao-elegante/), [functools](/blog/python-functools-lru-cache-partial-reduce/)). A meta não é listar cada método da API: é saber **qual variante usar** e onde o enum paga o custo cognitivo.

## Por que não basta uma string ou um int?

Três falhas clássicas do código “sem enum”:

1. **Typos silenciosos** — `"pendete"` passa em runtime e só aparece no relatório de cliente.
2. **Autocomplete zero** — o editor não sugere os estados válidos do domínio.
3. **Comparação frouxa** — `"Pago" == "pago"` falha; `1` e `"1"` se misturam em planilhas e CSVs brasileiros.

```python
# Frágil: strings mágicas
def pode_enviar(status: str) -> bool:
    return status in {"pago", "aprovado"}  # e se vier "PAGO"?

# Robusto: tipo fechado
from enum import Enum

class StatusPedido(Enum):
    PENDENTE = "pendente"
    PAGO = "pago"
    APROVADO = "aprovado"
    ENVIADO = "enviado"
    CANCELADO = "cancelado"

def pode_enviar(status: StatusPedido) -> bool:
    return status in {StatusPedido.PAGO, StatusPedido.APROVADO}
```

Com o enum, o type checker e o IDE avisam se você passar um valor inválido. Em code review, `StatusPedido.CANCELADO` comunica intenção melhor que `"cancelado"` solto no meio de um [pipeline ETL](/blog/etl-python-2026/).

## Enum básico: membros, name e value

```python
from enum import Enum

class CanalNotificacao(Enum):
    EMAIL = "email"
    SMS = "sms"
    PUSH = "push"
    WHATSAPP = "whatsapp"

print(CanalNotificacao.EMAIL)
# CanalNotificacao.EMAIL

print(CanalNotificacao.EMAIL.name)   # "EMAIL"
print(CanalNotificacao.EMAIL.value)  # "email"

# Lookup pelo valor:
print(CanalNotificacao("whatsapp"))
# CanalNotificacao.WHATSAPP

# Lookup pelo nome:
print(CanalNotificacao["SMS"])
# CanalNotificacao.SMS
```

Regras que importam no dia a dia:

- **Membros são singletons** — `CanalNotificacao.EMAIL is CanalNotificacao("email")` é `True`.
- **Iteração** devolve os membros na ordem de declaração.
- **Comparação** entre membros do mesmo enum usa identidade; comparar com o valor cru (`== "email"`) **falha** no `Enum` clássico.
- **Hashável** — serve como chave de `dict` e entra em `set` (útil com [Counter e defaultdict](/blog/collections-python-guia-completo/)).

```python
for canal in CanalNotificacao:
    print(canal.name, "→", canal.value)

rotulos = {
    CanalNotificacao.EMAIL: "E-mail",
    CanalNotificacao.WHATSAPP: "WhatsApp Business",
}
```

### Valores inválidos levantam ValueError

```python
try:
    CanalNotificacao("telegram")
except ValueError as exc:
    print(exc)
    # 'telegram' is not a valid CanalNotificacao
```

Na borda da aplicação (JSON de webhook, coluna de planilha, query string), capture `ValueError` e devolva 400 / mensagem amigável — o mesmo padrão de [validação com Pydantic](/blog/pydantic-validacao-dados-python/).

## StrEnum (Python 3.11+): o default moderno para APIs

A dor histórica do `Enum` com valor string é precisar de `.value` em todo lugar: logs, templates, JSON, comparação com dados externos. O `StrEnum` resolve isso herdando de `str` **e** de `Enum`:

```python
from enum import StrEnum, auto

class StatusPedido(StrEnum):
    PENDENTE = "pendente"
    PAGO = "pago"
    ENVIADO = "enviado"
    ENTREGUE = "entregue"
    CANCELADO = "cancelado"

status = StatusPedido.PAGO

print(status == "pago")          # True  ← diferença crucial
print(f"Pedido {status}")        # Pedido pago
print(status.upper())            # PAGO  ← métodos de str funcionam
print(isinstance(status, str))   # True
print(isinstance(status, StatusPedido))  # True
```

### auto() com StrEnum

Em `StrEnum`, `auto()` usa o **nome do membro em minúsculas** como valor — excelente para códigos estáveis sem digitar duas vezes:

```python
from enum import StrEnum, auto

class TipoDocumento(StrEnum):
    CPF = auto()       # "cpf"
    CNPJ = auto()      # "cnpj"
    NFE = auto()       # "nfe"
    BOLETO = auto()    # "boleto"

assert TipoDocumento.CNPJ == "cnpj"
```

Para domínios brasileiros (CPF/CNPJ, NF-e, boleto, PIX), `StrEnum` + `auto()` (ou valores explícitos em português sem acento no *value*) evita o drift entre banco, fila e frontend.

### Quando NÃO usar StrEnum

- O valor canônico **não** é texto (use `IntEnum` ou `Enum` com tuplas).
- Você precisa que `Status.ATIVO == "ativo"` seja **False** de propósito (menos comum).
- Precisa rodar em Python anterior ao 3.11 — aí use o padrão clássico `class Status(str, Enum):` (ver seção de compatibilidade).

## IntEnum e contadores estáveis

`IntEnum` faz o membro se comportar como `int`. Útil para códigos numéricos de legado, colunas de banco antigas e protocolos binários:

```python
from enum import IntEnum

class HttpStatusFamilia(IntEnum):
    INFORMACIONAL = 100
    SUCESSO = 200
    REDIRECIONAMENTO = 300
    ERRO_CLIENTE = 400
    ERRO_SERVIDOR = 500

print(HttpStatusFamilia.SUCESSO == 200)  # True
print(HttpStatusFamilia.SUCESSO + 1)     # 201
print(sorted(HttpStatusFamilia))         # ordena como int
```

Cuidado: `IntEnum` **mistura** com inteiros em comparações. Isso é prático e perigoso — um `if status == 200` passa sem deixar claro que o domínio é HTTP. Prefira `Enum`/`StrEnum` quando o número for só um detalhe de serialização.

## Flag e IntFlag: combinações de bits

Quando o usuário pode ter **várias permissões ao mesmo tempo**, `Flag` é a ferramenta certa:

```python
from enum import Flag, auto

class Permissao(Flag):
    LER = auto()
    ESCREVER = auto()
    EXECUTAR = auto()
    ADMIN = LER | ESCREVER | EXECUTAR

alice = Permissao.LER | Permissao.ESCREVER
print(alice)  # Permissao.LER|ESCREVER

print(Permissao.LER in alice)          # True
print(bool(alice & Permissao.EXECUTAR))  # False

# Revogar escrita:
alice = alice & ~Permissao.ESCREVER
print(alice)  # Permissao.LER
```

`IntFlag` é o irmão que também se comporta como `int` (útil se o banco grava a máscara numérica). Em feature flags de produto, combine com o guia de [feature flags em Python](/blog/feature-flags-python-deploy-seguro/) — o enum modela o *conjunto de flags conhecidas*; o store (Redis, config) decide o que está ligado para cada tenant.

## auto(), aliases e unicidade

```python
from enum import Enum, auto, unique

@unique  # impede valores duplicados (aliases)
class CorSemaforo(Enum):
    VERMELHO = auto()  # 1
    AMARELO = auto()   # 2
    VERDE = auto()     # 3

print(list(CorSemaforo))
```

Sem `@unique`, dois nomes com o mesmo valor criam um **alias** (o segundo nome aponta para o primeiro membro). Isso é útil para renomear com compatibilidade (`OK = SUCCESS = auto()` com valor compartilhado via atribuição explícita), mas confunde se for acidente.

Para controlar a geração de `auto()`:

```python
from enum import Enum, auto

class Prioridade(Enum):
    def _generate_next_value_(name, start, count, last_values):
        # Valores 10, 20, 30… em vez de 1, 2, 3
        return (count + 1) * 10

    BAIXA = auto()    # 10
    MEDIA = auto()    # 20
    ALTA = auto()     # 30
    CRITICA = auto()  # 40
```

## Métodos e propriedades no enum

Enums são classes: você pode adicionar helpers de domínio sem criar um service à parte.

```python
from enum import StrEnum

class StatusPedido(StrEnum):
    PENDENTE = "pendente"
    PAGO = "pago"
    ENVIADO = "enviado"
    ENTREGUE = "entregue"
    CANCELADO = "cancelado"

    @property
    def eh_final(self) -> bool:
        return self in {StatusPedido.ENTREGUE, StatusPedido.CANCELADO}

    def pode_transicionar_para(self, novo: "StatusPedido") -> bool:
        fluxos = {
            StatusPedido.PENDENTE: {StatusPedido.PAGO, StatusPedido.CANCELADO},
            StatusPedido.PAGO: {StatusPedido.ENVIADO, StatusPedido.CANCELADO},
            StatusPedido.ENVIADO: {StatusPedido.ENTREGUE},
            StatusPedido.ENTREGUE: set(),
            StatusPedido.CANCELADO: set(),
        }
        return novo in fluxos[self]

assert StatusPedido.PAGO.pode_transicionar_para(StatusPedido.ENVIADO)
assert not StatusPedido.CANCELADO.pode_transicionar_para(StatusPedido.PAGO)
```

Isso encaixa bem em [máquinas de estado simples](/blog/design-patterns-python/) e em testes com [pytest](/blog/testes-unitarios-python/) — a regra de negócio vive perto do tipo.

## Dataclass + Enum

O exemplo canônico de pedido que já aparece no guia de dataclasses fica mais claro com `StrEnum`:

```python
from dataclasses import dataclass, field
from datetime import datetime
from enum import StrEnum

class StatusPedido(StrEnum):
    PENDENTE = "pendente"
    PAGO = "pago"
    ENVIADO = "enviado"
    ENTREGUE = "entregue"

@dataclass
class ItemPedido:
    produto: str
    quantidade: int
    preco_unitario: float

    @property
    def subtotal(self) -> float:
        return self.quantidade * self.preco_unitario

@dataclass
class Pedido:
    cliente: str
    itens: list[ItemPedido] = field(default_factory=list)
    status: StatusPedido = StatusPedido.PENDENTE
    criado_em: datetime = field(default_factory=datetime.now)

    @property
    def total(self) -> float:
        return sum(item.subtotal for item in self.itens)

pedido = Pedido(cliente="Maria Silva")
pedido.status = StatusPedido.PAGO
print(pedido.status, pedido.status == "pago")  # pago True
```

Com `slots=True` e `frozen=True` nas dataclasses imutáveis, o enum como campo continua hashável e seguro para caches — combine com [`lru_cache` do functools](/blog/python-functools-lru-cache-partial-reduce/) só em funções puras.

## Pydantic e FastAPI

Pydantic entende `Enum`/`StrEnum` nativamente: valida entrada, documenta o `enum` no OpenAPI e serializa o valor.

```python
from enum import StrEnum
from pydantic import BaseModel, Field

class TipoChavePix(StrEnum):
    CPF = "cpf"
    CNPJ = "cnpj"
    EMAIL = "email"
    TELEFONE = "telefone"
    ALEATORIA = "aleatoria"

class CobrancaPix(BaseModel):
    valor_centavos: int = Field(gt=0)
    tipo_chave: TipoChavePix
    chave: str

cobranca = CobrancaPix(
    valor_centavos=1500,
    tipo_chave="email",  # aceito e convertido para TipoChavePix.EMAIL
    chave="financeiro@empresa.com.br",
)
print(cobranca.model_dump())
# {'valor_centavos': 1500, 'tipo_chave': <TipoChavePix.EMAIL: 'email'>, ...}
print(cobranca.model_dump(mode="json"))
# tipo_chave vira "email" no JSON
```

Em FastAPI, o mesmo tipo vira dropdown no Swagger e rejeita valores fora da lista — ideal para [APIs REST](/blog/apis-rest-com-fastapi/) e webhooks de [CRM](/guias/webhooks-fastapi-crm/). Para pagamentos, veja também a [integração PIX com Python](/blog/integracao-pagamentos-pix-python/).

```python
from fastapi import FastAPI
from pydantic import BaseModel
from enum import StrEnum

app = FastAPI()

class Ambiente(StrEnum):
    HOMOLOG = "homolog"
    PRODUCAO = "producao"

class ConfigNotificacao(BaseModel):
    ambiente: Ambiente
    canal: str

@app.post("/config")
def salvar_config(body: ConfigNotificacao) -> dict:
    return {"ok": True, "ambiente": body.ambiente}
```

## Pattern matching (match/case)

A partir do Python 3.10, `match` combina de forma limpa com enums — sem cascata de `if/elif` e sem strings mágicas:

```python
from enum import StrEnum

class EventoPedido(StrEnum):
    CRIADO = "criado"
    PAGO = "pago"
    ENVIADO = "enviado"
    CANCELADO = "cancelado"

def mensagem_cliente(evento: EventoPedido) -> str:
    match evento:
        case EventoPedido.CRIADO:
            return "Recebemos o seu pedido."
        case EventoPedido.PAGO:
            return "Pagamento confirmado. Vamos separar o estoque."
        case EventoPedido.ENVIADO:
            return "Seu pedido saiu para entrega."
        case EventoPedido.CANCELADO:
            return "Pedido cancelado. Se pagou, o estorno segue a política."
        case _:
            return "Atualização registrada."
```

Detalhes e armadilhas de esgotamento de padrões estão no guia de [pattern matching com match/case](/blog/pattern-matching-python-match-case/).

## Serialização: JSON, CSV, banco e logs

| Destino | Enum clássico | StrEnum / IntEnum |
|---------|---------------|-------------------|
| `json.dumps` | precisa de default/` .value` | `StrEnum` serializa como str; `IntEnum` como int |
| CSV / Excel | grave `.value` | idem; no Brasil use `;` e encoding corretos ([CSV](/blog/python-csv-leitura-escrita-arquivos/)) |
| SQL | coluna VARCHAR/INT + check | ORM mapeia o enum; prefira value estável |
| Logs | `f"{membro}"` → `Status.PAGO` | `StrEnum` loga o value; ou use `.name` de propósito |

```python
import json
from enum import Enum, StrEnum

class Legado(Enum):
    ATIVO = "ativo"

class Moderno(StrEnum):
    ATIVO = "ativo"

# Legado: TypeError sem default
print(json.dumps({"s": Moderno.ATIVO}))
# {"s": "ativo"}

print(json.dumps({"s": Legado.ATIVO}, default=lambda o: o.value))
# {"s": "ativo"}
```

**Nunca** persista `str(Legado.ATIVO)` se o formato for `"Legado.ATIVO"` — na leitura você não reconstrói o membro com `Legado("Legado.ATIVO")`. Grave **value** (ou name, se essa for a convenção documentada do time) e leia com `Legado(...)` / `Legado[name]`.

## Compatibilidade: Python anterior ao 3.11

Sem `StrEnum`, o padrão idiomático é herança múltipla com `str`:

```python
from enum import Enum

class StatusPedido(str, Enum):
    PENDENTE = "pendente"
    PAGO = "pago"
```

Comportamento próximo ao `StrEnum`, com diferenças sutis de subclassing documentadas no PEP 663. Em código novo com 3.11+, prefira `StrEnum` — a intenção fica explícita e o `auto()` gera value a partir do nome.

Para detectar a versão em runtime (útil em libs):

```python
import sys
from enum import Enum

if sys.version_info >= (3, 11):
    from enum import StrEnum
else:
    class StrEnum(str, Enum):
        """Polyfill mínimo para 3.10."""
```

## Enum × Literal × constantes de módulo

| Ferramenta | Quando usar |
|------------|-------------|
| `Enum` / `StrEnum` | Conjunto nomeado, iterável, com métodos de domínio, documentado no OpenAPI |
| `Literal["a", "b"]` | Tipagem estreita em funções sem runtime enum (mypy/pyright) |
| Constantes `STATUS_PAGO = "pago"` | Scripts curtos; evita se o conjunto cresce |

`Literal` e enum se complementam: você pode anotar `status: StatusPedido` e ainda usar `Literal` em APIs internas menores. Para times que adotam [ty / mypy / Pyrefly](/blog/tipagem-estatica-python-mypy/), o enum fecha o conjunto em **runtime e** em análise estática.

## Erros comuns (e como evitar)

**Comparar Enum clássico com string e achar que é bug do Python.** `Status.PAGO == "pago"` é `False` por design. Migre para `StrEnum` ou compare com o membro / `.value` de forma consciente.

**Usar `print(status)` em log e persistir `"Status.PAGO"`.** Separe representação de debug (`.name`) de valor de negócio (`.value`).

**Mutar `.value`.** Membros de enum não devem ser reassignados em runtime; trate-os como constantes.

**Enum gigante com 200 membros “só por organização”.** Se o conjunto muda toda semana (lista de municípios, catálogo de produtos), use tabela/banco ou arquivo de dados — enum é para conjuntos **estáveis**.

**Flag onde o estado é exclusivo.** `PENDENTE | PAGO` não faz sentido semântico; use `Enum` simples e uma máquina de estados.

**Importar o pacote PyPI `enum`.** Em ambientes legados isso mascara a stdlib. Use sempre a biblioteca padrão.

## Checklist rápido

1. **Estado exclusivo com valor texto de API?** → `StrEnum` (3.11+) ou `class X(str, Enum)`.
2. **Código numérico de legado/protocolo?** → `IntEnum`.
3. **Várias opções combináveis?** → `Flag` / `IntFlag`.
4. **Preciso de métodos de domínio (transições, labels)?** → métodos/`@property` no próprio enum.
5. **Validação de entrada HTTP/JSON?** → Pydantic + enum no campo.
6. **Conjunto instável ou enorme?** → não use enum; use dados.

## Conclusão

O `enum` é uma das peças da stdlib com melhor retorno em código de produto: elimina strings mágicas, documenta o domínio, melhora autocomplete e encaixa em dataclasses, Pydantic, FastAPI e `match/case`. Em 2026, o default sensato para APIs e sistemas brasileiros é **`StrEnum`** (ou `str, Enum` em 3.10); reserve `IntEnum` para códigos numéricos e `Flag` para permissões e máscaras.

Combine com [dataclasses](/blog/dataclasses-python-guia-completo/), [Pydantic](/blog/pydantic-validacao-dados-python/), [pattern matching](/blog/pattern-matching-python-match-case/), [tipagem estática](/blog/tipagem-estatica-python-mypy/) e as ferramentas de funções da stdlib ([functools](/blog/python-functools-lru-cache-partial-reduce/), [itertools](/blog/python-itertools-iteracao-elegante/)) para modelar o domínio sem framework extra. Na dúvida, troque o próximo `"pendente"` espalhado no código por um `Status.PENDENTE` — o type checker e o eu-do-futuro agradecem no mesmo PR.
