Null Object & Sentinels
None frequentemente significa tanto "ausente" quanto "valor vazio", o que causa bugs. Sentinelas são objetos marcadores únicos para "não fornecido". Objetos nulos implementam a mesma interface que colaboradores reais, mas não fazem nada - os chamadores pulam as verificações if obj is not None.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
from typing import Any
MISSING: Any = object()
def lookup(key: str, default=MISSING):
try:
return cache[key]
except KeyError:
if default is MISSING:
raise
return defaultQuando usar isso:
- Distinguir ausente de None - campos opcionais onde
Noneé um dado válido - Argumentos mutáveis padrão - nunca use
[]ou{}; useNoneou uma sentinela - Objeto nulo - logger, mailer ou cache no-op para funcionalidades desativadas
- Limites de API - semântica
default=MISSINGvsdefault=None - Enum.NONE - sentinela nomeada em APIs tipadas
Exemplo de Trabalho
Uma sentinela distingue configuração não definida de None explícito; um logger nulo pula I/O.
from dataclasses import dataclass
from typing import Any, Protocol
UNSET: Any = object()
@dataclass
class Config:
timeout: float | None | object = UNSET
def resolve_timeout(cfg: Config, fallback: float = 30.0) -> float:
if cfg.timeout is UNSET:
return fallback
if cfg.timeout is None:
raise ValueError("timeout explicitamente desabilitado")
return float(cfg.timeout)
class Logger(Protocol):
def info(self, msg: str) -> None: ...
@dataclass
class NullLogger:
def info(self, msg: str) -> None:
pass
@dataclass
class StdoutLogger:
def info(self, msg: str) -> None:
print(msg)
def run_job(logger: Logger | None = None) -> None:
log = logger if logger is not None else NullLogger()
log.info("starting")
log.info("done")
print(resolve_timeout(Config())) # 30.0
print(resolve_timeout(Config(timeout=5.0))) # 5.0
run_job() # silencioso
run_job(StdoutLogger())O que isso demonstra:
UNSETnão é igual a nenhum valor do usuário, incluindoNoneNoneexplícito pode significar "desabilitado" com comportamento diferente de não definidoNullLoggerimplementa a interface com no-ops- Dependência opcional padrão para objeto nulo em tempo de execução
Mergulho Profundo
Como Funciona
object()cria uma identidade única; verificaçõesissão O(1) e inequívocas- Membros
enum.Enumpodem servir como sentinelas nomeadas em APIs públicas - Padrão de objeto nulo: substitui uma implementação que não faz nada em vez de ramificar
dataclasses.MISSING(3.13+) documenta o estado do campo em algumas APIs- Verificadores de tipo precisam de
Literalou sobrecargas quando sentinelas afetam tipos de retorno
Sentinela vs None vs Optional
| Valor | Significado |
|---|---|
UNSET | O chamador não passou o argumento |
None | O chamador passou um valor semântico vazio/ausente |
Optional[T] | O valor pode legitimamente ser None |
Notas Python
from enum import auto, Enum
class _Sentinel(Enum):
MISSING = auto()
MISSING = _Sentinel.MISSING # repr nomeado na saída de depuraçãoArmadilhas
- Vazamento de sentinela em dados serializados - JSON não pode representar
object(). Correção: converter paranullou omitir a chave na fronteira da API. - Verificações de igualdade com
==- sentinelas devem usaris, não==. Correção: documentar e usar lint parais UNSET. - Uso excessivo de objetos nulos - mascarando bugs reais quando a dependência deveria ser obrigatória. Correção: exigir dependências não nulas na orquestração de produção.
- Sentinelas mutáveis singleton - nunca anexe estado ao objeto sentinela. Correção: usar membros
enumimutáveis. - Confundir
default=Noneedefault=UNSET- a documentação da API deve declarar as semânticas claramente. Correção: nomear parâmetrostimeout_or_unset.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Apenas Optional | None é a única semântica de ausência | Ausente vs vazio devem diferir |
Apenas **kwargs | Campos de extensão verdadeiramente opcionais | Parâmetros principais precisam de clareza posicional |
NotRequired TypedDict | Dicionários parciais semelhantes a JSON | Parâmetros de função |
| Levantar exceção ao faltar | Valores obrigatórios | Opcional com três estados |
FAQs
Por que não usar None para ausente e apenas ausente?
Quando None é um dado válido (coluna de banco de dados anulável), você precisa de um terceiro estado para "argumento não passado".
object() é a sentinela padrão?
Comum em padrões da biblioteca padrão (getattr(obj, name, default) usa sua própria sentinela internamente). APIs públicas frequentemente preferem enums nomeados para clareza.
O que é um objeto nulo?
Uma classe que implementa a mesma interface que serviços reais, mas não realiza nenhum trabalho - evita verificações de None nos pontos de chamada.
Como os verificadores de tipo lidam com sentinelas?
Use overload em funções onde o tipo de retorno depende se o padrão foi fornecido, ou T | Literal[_MISSING].
Pydantic pode usar sentinelas?
Use model_fields_set para detectar campos não definidos em vez de sentinelas personalizadas - Pydantic v2 rastreia o que foi fornecido.
Argumentos mutáveis padrão?
Nunca def f(items=[]). Use None e items = [] if items is None else items, ou uma sentinela mais um factory.
Sentinela vs Ellipsis (...)?
... aparece em fatiamento e arquivos de stub. Raramente usado como sentinela em tempo de execução - object() ou Enum é mais claro.
Como testar caminhos de sentinela?
Afirme o comportamento quando o parâmetro for omitido, quando None for passado e quando um valor real for passado - três casos distintos.
Objeto nulo vs dependência opcional?
Objeto nulo sempre chamável; dependência opcional pode pular totalmente o recurso. Escolha com base no custo de ramificação no ponto de chamada.
functools.lru_cache precisa de sentinelas?
Distinga "não cacheado" de None cacheado usando uma sentinela privada em implementações de wrapper - o mesmo padrão.
Relacionados
- EAFP vs LBYL - tratamento de chaves ausentes
- Injeção de Dependência em Python - objeto nulo como dep padrão
- Padrões Factory & Builder - campos opcionais em builders
- Noções Básicas de Padrões Pythonicos - visão geral da seção
Versões da Pilha: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.