Aquí hay algo que sorprende a casi todo el mundo cuando lo descubre: and y or en Python no devuelven True o False. Devuelven uno de sus operandos. Literalmente. Y una vez que entiendes por qué, empiezas a ver código Python de una manera completamente diferente.
La decisión de diseño es elegante: Python evalúa cada operando como “verdadero” o “falso” (usando las mismas reglas de truthiness que un if), y devuelve el operando que determinó el resultado. Así, si tienes "hola" or "default", Python evalúa "hola", ve que es truthy, y como eso ya determina que la expresión or es verdadera, devuelve "hola" directamente, sin ni siquiera mirar "default". Eso es la evaluación de cortocircuito (short-circuit evaluation).
¿Cuándo usarlo? Constantemente: valores por defecto, guardar accesos a métodos en objetos que podrían ser None, inicialización condicional. ¿Qué pasa si lo entiendes mal? Que escribes x or y esperando un bool y recibes un string, un número, o None, lo cual puede romper validaciones posteriores de una forma que no es obvia.
not es el único de los tres que siempre devuelve un bool. Siempre. Es tu forma de forzar una conversión explícita si la necesitas.
# Comportamiento completo de and, or, not en Python
# ── Reglas de truthiness ──────────────────────────────────────────
# Son "falsy": None, 0, 0.0, "", [], {}, set(), False
# Todo lo demás es "truthy"
# ── or: devuelve el primer operando truthy, o el último ──────────
print("hola" or "default") # "hola" — primero es truthy, cortocircuito
print("" or "default") # "default" — primero es falsy, evalúa el segundo
print("" or 0 or "backup") # "backup" — recorre hasta encontrar truthy
print("" or 0 or None) # None — todos falsy, devuelve el último
# ── and: devuelve el primer operando falsy, o el último ──────────
print("hola" and "mundo") # "mundo" — primero es truthy, evalúa el segundo
print("" and "mundo") # "" — primero es falsy, cortocircuito
print(None and 1 / 0) # None — cortocircuito: 1/0 nunca se ejecuta
# ── not: siempre devuelve bool ───────────────────────────────────
print(not "hola") # False
print(not "") # True
print(not None) # True
print(type(not "hola")) # <class 'bool'>
# ── Patrón práctico: acceder a método solo si el objeto no es None ──
def process(name):
# Si name es None o "", upper() nunca se llama → sin AttributeError
return name and name.upper()
print(process("python")) # "PYTHON"
print(process(None)) # None — and cortocircuita en None (falsy)
print(process("")) # "" — and cortocircuita en "" (falsy)
# ── Patrón práctico: valor por defecto ────────────────────────────
def greet(username=None):
display_name = username or "Anónimo"
return f"Hola, {display_name}!"
print(greet("María")) # "Hola, María!"
print(greet()) # "Hola, Anónimo!"
print(greet("")) # "Hola, Anónimo!" — "" es falsy, usa el default
# ── Forzar bool explícitamente ────────────────────────────────────
value = "algo"
as_bool = bool(value) # True — más explícito que not not value
flag = not not value # True — idiomático pero menos legible
Desglose del comportamiento
La clave de or es esta regla: devuelve el primer operando truthy que encuentre; si ninguno lo es, devuelve el último. Por eso "" or 0 or None devuelve None: recorrió toda la lista sin encontrar nada truthy y entregó lo último. Esto convierte a or en un mecanismo natural para cascadas de valores por defecto.
and funciona al revés: devuelve el primer operando falsy que encuentre; si todos son truthy, devuelve el último. La línea None and 1 / 0 demuestra algo importante: la división por cero nunca ocurre. Python ve None (falsy), sabe que la expresión and ya no puede ser verdadera, y devuelve None sin evaluar el lado derecho. Este cortocircuito no es un detalle menor; es lo que hace seguro el patrón name and name.upper().
Ese patrón merece atención especial. Si name es None, None and name.upper() cortocircuita y devuelve None. Si name es "", "" and name.upper() cortocircuita y devuelve "". Solo cuando name tiene contenido real llega a ejecutar .upper(). Es un guardián de una sola línea.
not rompe el patrón: no importa qué le pases, siempre obtienes True o False. Si en algún momento necesitas que una expresión lógica produzca un bool real (por ejemplo, para guardarlo en un campo de base de datos), bool(x) o not not x son tus opciones, siendo bool(x) la más legible.
Errores que debes conocer
Error: usar or para valores por defecto cuando 0 o "" son valores válidos, no casos de “sin dato”.
# ❌ Wrong
def set_timeout(seconds=None):
timeout = seconds or 30 # Si seconds=0, lo trata como "no hay valor"
return timeout
print(set_timeout(0)) # 30 — incorrecto, 0 era intencional
# ✅ Right
def set_timeout(seconds=None):
timeout = seconds if seconds is not None else 30
return timeout
print(set_timeout(0)) # 0 — respeta el cero explícito
El check is not None distingue “no se pasó nada” de “se pasó el valor cero”, que son semánticamente distintos.
Error: esperar un bool de and u or y usarlo en código que exige el tipo exactamente.
# ❌ Wrong
def is_valid(text):
return text and len(text) > 0 # Devuelve text (string) o False, no siempre bool
result = is_valid("hola")
print(type(result)) # <class 'bool'> — aquí sí, pero...
print(is_valid("")) # "" — tipo str, no False
# ✅ Right
def is_valid(text):
return bool(text and len(text) > 0) # bool() garantiza el tipo
print(is_valid("")) # False — tipo bool garantizado
Envolver con bool() es el seguro cuando la función promete devolver un booleano y la lógica interna usa and/or.
N° 26