Objeto Nulo y Centinelas
None a menudo significa tanto "ausente" como "valor vacío", lo que causa errores. Los centinelas son objetos marcadores únicos para "no proporcionado". Los objetos nulos implementan la misma interfaz que los colaboradores reales pero no hacen nada; las llamadas omiten las comprobaciones if obj is not None.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
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 defaultCuándo recurrir a esto:
- Distinguir ausente de None - campos opcionales donde
Nonees un dato válido - Argumentos mutables por defecto - nunca uses
[]o{}; usaNoneo un centinela - Objeto nulo - logger, mailer o caché que no hace nada para funciones deshabilitadas
- Límites de API - semántica de
default=MISSINGvsdefault=None - Enum.NONE - centinela con nombre en APIs tipadas
Ejemplo de Trabajo
Un centinela distingue una configuración no establecida de un None explícito; un logger nulo omite la E/S.
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 explícitamente deshabilitado")
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())Lo que esto demuestra:
UNSETno es igual a ningún valor del usuario, incluidoNoneNoneexplícito puede significar "deshabilitado" con un comportamiento diferente al de no establecidoNullLoggerimplementa la interfaz sin hacer nada- La dependencia opcional por defecto es un objeto nulo en tiempo de ejecución
Inmersión Profunda
Cómo Funciona
object()crea una identidad única; las comprobacionesisson O(1) y sin ambigüedades- Los miembros de
enum.Enumpueden servir como centinelas con nombre en APIs públicas - Patrón de objeto nulo: sustituir una implementación que no hace nada en lugar de ramificar
dataclasses.MISSING(3.13+) documenta el estado del campo en algunas APIs- Los verificadores de tipos necesitan
Literalo sobrecargas cuando los centinelas afectan a los tipos de retorno
Centinela vs None vs Optional
| Valor | Significado |
|---|---|
UNSET | El llamador no pasó el argumento |
None | El llamador pasó un valor semántico vacío/ausente |
Optional[T] | El valor puede ser legítimamente None |
Notas de Python
from enum import auto, Enum
class _Sentinel(Enum):
MISSING = auto()
MISSING = _Sentinel.MISSING # representación con nombre en la salida de depuraciónTrampas
- Fugas de centinelas en datos serializados - JSON no puede representar
object(). Solución: convertir anullu omitir la clave en el límite de la API. - Comprobaciones de igualdad con
==- los centinelas deben usaris, no==. Solución: documentar y usar linting parais UNSET. - Abuso de objetos nulos - enmascarar errores reales cuando la dependencia debería ser requerida. Solución: requerir dependencias no nulas en la configuración de producción.
- Singleton de centinela mutable - nunca adjuntar estado al objeto centinela. Solución: usar miembros inmutables de
enum. - Confundir
default=Noneydefault=UNSET- la documentación de la API debe indicar claramente las semánticas. Solución: nombrar parámetrostimeout_or_unset.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Solo Optional | None es la única semántica de ausencia | Ausente vs vacío deben diferir |
Solo **kwargs | Campos de extensión verdaderamente opcionales | Los parámetros principales necesitan claridad posicional |
NotRequired TypedDict | Diccionarios parciales tipo JSON | Parámetros de función |
| Lanzar excepción al faltar | Valores requeridos | Opcional con tres estados |
Preguntas Frecuentes
¿Por qué no usar None solo para ausente?
Cuando None es un dato válido (columna de base de datos anulable), necesitas un tercer estado para "argumento no pasado".
¿Es object() el centinela estándar?
Común en patrones de la biblioteca estándar (getattr(obj, name, default) usa su propio centinela internamente). Las APIs públicas a menudo prefieren enums con nombre para mayor claridad.
¿Qué es un objeto nulo?
Una clase que implementa la misma interfaz que los servicios reales pero que no realiza ningún trabajo; evita comprobaciones de None en los puntos de llamada.
¿Cómo manejan los verificadores de tipos los centinelas?
Usa overload en funciones donde el tipo de retorno depende de si se proporcionó el valor por defecto, o una unión T | Literal[_MISSING].
¿Puede Pydantic usar centinelas?
Usa model_fields_set para detectar campos no establecidos en lugar de centinelas personalizados; Pydantic v2 rastrea lo que se proporcionó.
¿Argumentos mutables por defecto?
Nunca def f(items=[]). Usa None y items = [] if items is None else items, o un centinela más una fábrica.
¿Centinela vs Elipsis (...)?
... aparece en el slicing y en archivos stub. Raramente se usa como centinela en tiempo de ejecución; object() o Enum es más claro.
¿Cómo pruebo las rutas de los centinelas?
Afirma el comportamiento cuando el parámetro se omite, cuando se pasa None y cuando se pasa un valor real; tres casos distintos.
¿Objeto nulo vs dependencia opcional?
El objeto nulo siempre es llamable; la dependencia opcional puede omitir la característica por completo. Elige según el costo de ramificación en el punto de llamada.
¿Necesita functools.lru_cache centinelas?
Distingue "no en caché" de None en caché usando un centinela privado en implementaciones de envoltura; el mismo patrón.
Relacionado
- EAFP vs LBYL - manejo de claves ausentes
- Inyección de Dependencias en Python - objeto nulo como dependencia por defecto
- Patrones Factory y Builder - campos opcionales en builders
- Conceptos Básicos de Patrones Pythonicos - resumen de la sección
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 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+, y uv 0.6+.