Las 6 constantes raras de Python: True, None y __debug__

Las 6 constantes "raras" que Python trae pre-declaradas

Python viene con seis valores pre-declarados que aparecen en la documentación bajo el título de "Built-in Constants": True, False, None, __debug__, Ellipsis y NotImplemented. El problema es que no todos son iguales, y la mayoría de los desarrolladores los usa sin entender por qué algunos son imposibles de reasignar mientras que otros se pueden sobrescribir sin que el intérprete se queje. Esa inconsistencia no es accidental: tiene historia, y revisarla ayuda a escribir mejor código backend.

True, False y None: los únicos keywords-constante

True, False y None son los tres casos más extraños del lenguaje: no son identificadores resueltos por el sistema de nombres, sino tokens léxicos propios del lexer. Esa es la razón por la que x.True = 1 lanza SyntaxError y por la que True = 67 directamente no compila. No hay ningún otro caso así en Python.

El camino hasta aquí fue accidentado. Cuando Guido van Rossum introdujo el tipo bool en Python 2.2 mediante el PEP 285 (marzo de 2002), True y False eran simplemente dos builtins más. Eso significaba que, en Python 2, podías hacer esto:

👥 ¿Quieres ir más allá de la noticia?

En nuestra comunidad discutimos las tendencias, compartimos oportunidades y nos ayudamos entre emprendedores. Sin humo, solo acción.

👥 Unirme a la comunidad
True = "ya no es booleano"

Y el intérprete te dejaba seguir. La consecuencia práctica eran bugs silenciosos: un loop con una variable llamada True, una asignación accidental en un for, una redefinición maliciosa en código de terceros que rompía todas las validaciones posteriores sin levantar excepciones. La propia PEP 285 documenta casos donde equipos recibían resultados incoherentes durante meses sin entender por qué.

En Python 3.0, True, False y None se elevaron a palabras reservadas. El cambio fue tan profundo que cualquier intento de reasignarlos (incluso como nombre de parámetro o como for True in ...) se rechaza en la fase de parsing, antes de ejecutar nada. La documentación oficial de Python lo confirma: "Assignments to True are illegal and raise a SyntaxError."

debug: el identificador que no se puede asignar

__debug__ es la rareza intermedia. No es un keyword, sino un identificador normal, pero el intérprete lo trata como caso especial:

  • Vale True por defecto
  • Vale False cuando ejecutas Python con la bandera -O (modo "optimizado", que también desactiva los assert)
  • No se le puede asignar nada: __debug__ = 67 lanza SyntaxError
  • Ni siquiera como atributo: x.__debug__ = 67 también falla

Esto lo hace especialmente útil para envolver comprobaciones costosas:

if __debug__:
    validar_invariante_costosa()

En builds de producción (con -O), el bloque entero desaparece sin penalización de runtime. Es un patrón que muchos developers juniors no conocen, pero que sigue siendo la forma idiomática de hacer "asserts que se pueden desactivar sin tocar el código".

Ellipsis y NotImplemented: las constantes que no lo son

Aquí viene la parte más confusa. Ellipsis y NotImplemented aparecen en la misma sección de la documentación oficial que True y False, pero no son constantes reales: son builtins normales que se pueden sobrescribir.

>>> NotImplemented = 67
>>> NotImplemented
67

Ningún error. El identificador queda vinculado al entero 67 y el resto del código que esperaba un NotImplemented empieza a comportarse de forma inesperada. Lo mismo ocurre con Ellipsis.

Pero ... (el literal de tres puntos) sí se mantiene aunque reasignes Ellipsis. Esto se debe a que ... es su propio token léxico, igual que True, mientras que Ellipsis es solo el nombre del singleton al que apunta ese token. La asimetría entre el literal y su nombre es la que explota la mayoría de developers la primera vez que la ve.

Sobre NotImplemented, la propia documentación advierte: nunca debe evaluarse en contexto booleano. Existe para que los métodos mágicos (__add__, __eq__, etc.) puedan decirle al intérprete "no sé hacer esta operación con este tipo" y dejar que el sistema pruebe con la operación reflejada del otro operando. Desde Python 3.9, evaluarlo como booleano está deprecado.

Por qué Python 3 blindó True y False

El argumento central del cambio de Python 2 a Python 3 fue de seguridad y filosofía. Permitir que True pudiera reasignarse abría tres categorías de problemas:

  • Bugs accidentales: una variable de loop llamada True que sobrescribía el global sin warning. La PEP 285 ya mencionaba el caso if x == True que confundía a principiantes; sumarle la posibilidad de reasignación empeoraba todo.
  • Sabotaje silencioso: en código no revisado, un True = 0 en un módulo importado rompía todas las validaciones posteriores sin levantar excepciones. Cualquier check de seguridad escrito como if user_is_admin == True quedaba neutralizado.
  • Violación del Zen de Python: "Explicit is better than implicit". Si True podía no ser True, el lenguaje estaba mintiendo al lector.

El cambio a keyword elimina esa categoría entera de fallos: si algo reasigna True, el código no llega a ejecutarse.

Qué significa esto para tu startup

Para un equipo técnico que mantiene código Python en producción, estas rarezas tienen impacto real:

  • Audita migraciones desde Python 2. Si tienes módulos legacy que reasignan True, False o None, esos fallarán al subir a Python 3 con SyntaxError. Es una señal útil: te dice exactamente qué partes del código estaban asumiendo algo que el lenguaje nunca garantizó.
  • Usa if __debug__ para lógica condicional de desarrollo. Si tienes asserts pesados, precondiciones o trazas que solo aportan valor en desarrollo, esta es la forma idiomática de incluirlos sin penalizar producción. Es más limpio que # DEBUG: comentado.
  • No uses Ellipsis ni NotImplemented como nombres de variable. Aunque el lenguaje lo permite, hacerlo rompe silenciosamente la resolución de operadores en otros módulos que dependan del comportamiento por defecto. Linters como ruff y pylint lo marcan por defecto.

Conclusión

Lo que parece un capricho histórico del diseño de Python es en realidad un rastro de cómo el lenguaje evolucionó: True y False pasaron de ser variables reasignables (con todos los bugs que eso causaba) a ser tokens léxicos blindados; __debug__ quedó como caso especial intermedio; y Ellipsis/NotImplemented siguen siendo los patitos feos que viven en la sección de constantes sin ser constantes. Entender la diferencia no es academicismo: es la diferencia entre escribir código que sobrevive a una auditoría de seguridad y código que se rompe de formas difíciles de reproducir.

Fuentes

👥 ¿Quieres ir más allá de la noticia?

En nuestra comunidad discutimos las tendencias, compartimos oportunidades y nos ayudamos entre emprendedores. Sin humo, solo acción.

👥 Unirme a la comunidad

Daily Shot: Tu ventaja táctica

Lo que pasó en las últimas 24 horas, resumido para que tú no tengas que filtrarlo.

Suscríbete para recibir cada mañana la curaduría definitiva del ecosistema startup e inversionista. Sin ruido ni rodeos, solo la información estratégica que necesitas para avanzar:

  • Venture Capital & Inversiones: Rondas, fondos y movimientos de capital.
  • IA & Tecnología: Tendencias, Web3 y herramientas de automatización.
  • Modelos de Negocio: Actualidad en SaaS, Fintech y Cripto.
  • Propósito: Erradicar el estancamiento informativo dándote claridad desde tu primer café.

📡 El Daily Shot Startupero

Noticias del ecosistema startup en 2 minutos. Gratis, todos los días.

Share to...