En Python, bool no es un tipo independiente que “se parece” a un número: es literalmente una subclase de int. Eso significa que True y False no son solo valores de verdad — son los enteros 1 y 0 con un nombre más expresivo. No es un detalle de implementación ni una coincidencia: es parte de la especificación del lenguaje.
>>> isinstance(True, int) True >>> type(True) <class 'bool'> >>> True == 1 True >>> False == 0 True
¿Por qué funciona así? La decisión de diseño viene de Python 2.3, cuando se añadió bool al lenguaje. En lugar de crear un tipo completamente nuevo y romper código existente que ya usaba 0 y 1 como valores de verdad, se optó por hacer que bool heredara de int. Así la compatibilidad hacia atrás quedaba garantizada y el comportamiento era predecible. True y False son objetos singleton de tipo bool, pero el sistema de tipos de Python los reconoce como enteros en cualquier contexto donde se espere un int.
La jerarquía real es bool → int → object. Cuando Python evalúa True + True, no hay ninguna magia: simplemente suma 1 + 1 usando la aritmética de int. El resultado es 2, de tipo int — no bool.
Esto importa en producción porque hay un patrón idiomático que depende exactamente de este comportamiento: contar cuántos elementos de una colección cumplen una condición. Y también hay un conjunto de bugs silenciosos esperando a quien no lo sabe.
Ejemplo completo
from typing import Sequence
def analyze_temperatures(readings: Sequence[float]) -> dict:
above_zero = sum(x > 0 for x in readings)
below_zero = sum(x < 0 for x in readings)
exactly_zero = sum(x == 0 for x in readings)
# bool actúa como int en índices también
flags = [True, False, True, True, False]
active_count = sum(flags) # suma directa de una lista de bools
# isinstance revela la herencia
for val in [True, False, 42]:
print(f"{val!r:>6} | type: {type(val).__name__:<5} | isinstance(val, int): {isinstance(val, int)}")
# Demostración aritmética explícita
print(f"\nTrue + True = {True + True}")
print(f"True + True + 1 = {True + True + 1}")
print(f"type(True + True) = {type(True + True)}") # int, no bool
# El valor de retorno con bool como entero en diccionario
severity = {0: "ok", 1: "warning", 2: "critical"}
level = (above_zero == 0) + (below_zero > len(readings) // 2)
# level es 0, 1 o 2 según las condiciones; se usa directamente como clave
return {
"total": len(readings),
"above_zero": above_zero,
"below_zero": below_zero,
"exactly_zero": exactly_zero,
"active_flags": active_count,
"severity": severity.get(level, "unknown"),
}
if __name__ == "__main__":
data = [3.2, -1.1, 0.0, 5.5, -0.3, 0.0, 7.1]
result = analyze_temperatures(data)
for k, v in result.items():
print(f"{k}: {v}")
Lo que está pasando aquí
sum(x > 0 for x in readings) es el corazón del ejemplo. La expresión generadora produce True o False para cada elemento, y sum() los acumula como si fueran 1 y 0. El resultado es exactamente el número de elementos que cumplen la condición — sin if, sin contador manual, sin len([x for x in readings if x > 0]). Es conciso porque el lenguaje garantiza que bool se comporta como int en contextos aritméticos.
Fíjate en type(True + True) — el resultado es int, no bool. Cuando bool participa en operaciones aritméticas, Python asciende al tipo base y devuelve int. El único momento en que obtienes bool es cuando el operador específicamente produce un valor de verdad (comparaciones, and, or, not).
La línea level = (above_zero == 0) + (below_zero > len(readings) // 2) muestra cómo combinar condiciones booleanas en un índice o nivel numérico. Cada comparación produce True/False, la suma da 0, 1 o 2, y eso se usa directamente como clave de diccionario. Esto es idiomático cuando las condiciones tienen peso uniforme y el resultado es un nivel ordinal.
El bloque con isinstance confirma algo que no es intuitivo a primera vista: isinstance(True, int) devuelve True, pero isinstance(42, bool) devuelve False. La herencia es unidireccional — todo bool es un int, pero no al revés.
Errores que debes conocer
Error: usar True/False como claves de diccionario junto con 1/0, esperando que sean entradas separadas, cuando en realidad son la misma clave.
# ❌ Wrong
d = {}
d[True] = "verdadero"
d[1] = "uno"
print(d) # {True: "uno"} — solo hay UNA entrada, la segunda sobreescribió la primera
print(len(d)) # 1, no 2
# ✅ Right
d = {}
d["true"] = "verdadero"
d[1] = "uno"
print(d) # {"true": "verdadero", 1: "uno"}
print(len(d)) # 2
True == 1 y hash(True) == hash(1), así que para los diccionarios y conjuntos son la misma clave. Usa strings u otros tipos si necesitas distinguirlos.
Error: comparar con == True en vez de evaluar directamente, lo que produce resultados inesperados cuando el valor es un entero.
# ❌ Wrong
def is_active(value):
return value == True # True solo si value es exactamente True o 1
print(is_active(2)) # False — aunque 2 es "truthy"
print(is_active("yes")) # False — aunque es truthy
# ✅ Right
def is_active(value):
return bool(value) # convierte explícitamente, respeta la semántica de verdad
print(is_active(2)) # True
print(is_active("yes")) # True
value == True compara aritméticamente (solo 1 y True son iguales a True); bool(value) evalúa la veracidad completa del objeto, que es lo que normalmente se quiere.
Error: asumir que isinstance(x, bool) es suficiente para filtrar booleanos fuera de una lista de enteros, olvidando que el orden de comprobación importa.
# ❌ Wrong
def process(value):
if isinstance(value, int): # True y False también entran aquí
return value * 2
return None
print(process(True)) # 2, no None — bool pasó el filtro de int
# ✅ Right
def process(value):
if isinstance(value, bool): # bool primero, porque es subclase de int
return None
if isinstance(value, int):
return value * 2
return None
print(process(True)) # None
print(process(5)) # 10
Hay que comprobar bool antes que int; de lo contrario, la comprobación de int captura también los booleanos por la herencia.
El patrón sum(condition for x in iterable) es una de esas herramientas que una vez que la ves, no puedes dejar de usarla — y ahora sabes exactamente por qué funciona.
N° 22