Evaluación de cortocircuito — Short-circuit Evaluation
Al finalizar este tema
Comprenderá cómo funcionan internamente los and/or de Python, podrá aplicar patrones prácticos que aprovechan la evaluación de cortocircuito y explicará por qué el orden es importante.
¿Qué es la evaluación de cortocircuito?
La evaluación de cortocircuito (Short-circuit Evaluation) consiste en dejar de evaluar los operandos restantes una vez que el resultado de una operación lógica queda determinado.
# and: si el primer valor es False, no evalúa el segundoFalse and print("This never runs") # False
# or: si el primer valor es True, no evalúa el segundoTrue or print("This never runs") # True¿Por qué funciona así? Lógicamente, es de esperarse:
and: Si alguno es False, el resultado total es False. Si el primero es False, no hace falta evaluar el segundo.or: Si alguno es True, el resultado total es True. Si el primero es True, no hace falta evaluar el segundo.
Comportamiento de and
# and devuelve el primer valor falsy o el último valorprint(0 and 5) # 0 (0 es falsy → se detiene aquí)print("" and "hello") # "" (cadena vacía es falsy)print(None and 42) # None (None es falsy)
print(1 and 5) # 5 (1 es truthy → continúa → devuelve 5)print("hello" and 42) # 42 (ambos son truthy → devuelve el último valor)print(3 and 2 and 1) # 1 (todos son truthy → devuelve el último valor)print(3 and 0 and 1) # 0 (se detiene en 0)Clave: and devuelve el primer valor falso. Si todos son verdaderos, devuelve el último valor.
Valores falsos en Python
# Todos estos se evalúan como Falsebool(False) # Falsebool(0) # Falsebool(0.0) # Falsebool("") # Falsebool(None) # Falsebool([]) # False (lista vacía)bool({}) # False (diccionario vacío)
# El resto se evalúa como Truebool(1) # Truebool("hello") # Truebool([1, 2]) # TrueFuncionamiento de OR
# or devuelve el primer valor truthy o, si no hay ninguno, el último valorprint(0 or 5) # 5 (0 es falsy → pasa al siguiente → devuelve 5)print("" or "hello") # "hello" (cadena vacía es falsy → devuelve "hello")print(None or 42) # 42
print(1 or 5) # 1 (1 es truthy → se detiene aquí)print("hi" or "bye") # "hi" (el primero es truthy)print(0 or "" or None) # None (todos son falsy → devuelve el último valor)print(0 or "" or 42) # 42 (primer valor truthy)Clave: or devuelve el primer valor truthy. Si todos son falsy, devuelve el último valor.
Patrón práctico: configuración de valores predeterminados
# Si el usuario no ingresa un nombre, usar "Guest"username = input("Enter name: ") or "Guest"print(f"Hello, {username}")
# Si no hay valor de configuración, usar el predeterminadoconfig = {}db_host = config.get("host") or "localhost"db_port = config.get("port") or 5432Este es un patrón que establece el valor predeterminado mediante or. Si el lado izquierdo es falsy (cadena vacía, None, etc.), se utiliza el valor del lado derecho. Este mismo patrón se usa frecuentemente en JavaScript.
Precaución: este patrón es peligroso cuando 0 o una cadena vacía son valores válidos.
# Error: 0 es falso, por lo que se aplica el valor predeterminadocount = 0result = count or 10 # 10 (intención: debería ser 0)
# Solución: comparación explícitaresult = count if count is not None else 10 # 0Patrón práctico — Ejecución condicional
# Ejecución condicional con and (reemplaza a if)data = [1, 2, 3]data and print(f"Data has {len(data)} items") # Se imprime
empty = []empty and print("This won't print") # No se imprime (lista vacía = falso)# Acceso seguro a atributosuser = {"name": "Hoon", "address": None}
# Si address es None, no se llama a .get()city = user.get("address") and user["address"].get("city")print(city) # None (and se detiene en None)La importancia del orden
El núcleo de la evaluación de cortocircuito es colocar las operaciones costosas al final.
def is_valid_user(user_id): """Consulta de BD — operación lenta""" print(f"Querying DB for user {user_id}...") return user_id > 0
def has_permission(user_id, action): """Verificación de permisos — aún más lento""" print(f"Checking permission for {action}...") return True
# Bien: Realizar la comprobación rápida primerouser_id = -1if user_id > 0 and is_valid_user(user_id) and has_permission(user_id, "read"): print("Access granted")# Salida: (no se imprime nada — user_id > 0 es False, por lo que no se consulta la BD)
# Mal: Realizar la comprobación lenta primeroif is_valid_user(user_id) and user_id > 0: print("Access granted")# Salida: "Consultando BD para el usuario -1..." (se produce una consulta innecesaria a la BD)También es útil para prevenir errores:
# Error al indexar cuando la lista está vacíaitems = []
# Mal: Se produce IndexError# if items[0] > 10:
# Bien: Comprobar si la lista está vacía primeroif items and items[0] > 10: print("First item is large")# items es falsy (lista vacía), por lo que items[0] no se ejecutaPuntos a tener en cuenta al depurar
La evaluación de cortocircuito puede dificultar la depuración.
def check_permission(user): print(f"Checking permission for {user['name']}") return user.get("role") == "admin"
def validate_input(data): print(f"Validating: {data}") return len(data) > 0
user = {"name": "Hoon", "role": "admin"}data = "hello"
if check_permission(user) or validate_input(data): print("Granted")
# Salida:# Checking permission for Hoon# Granted# → validate_input no se ejecuta! (tampoco se imprime)En or, si la primera condición es verdadera, la segunda función ni siquiera se invoca. Si coloca funciones con efectos secundarios (como registro de eventos o incremento de contadores) en una expresión condicional, su ejecución dependerá del resultado anterior. Si los efectos secundarios deben ejecutarse siempre, invoque las funciones fuera de la expresión condicional primero.
Comparación con otros lenguajes
// JavaScript — mismo comportamiento
const name = userInput || "Guest";
const port = config.port || 3000;
// JavaScript ES2020: ?? (Nullish Coalescing)
const count = 0 ?? 10; // 0 (?? solo verifica null/undefined)
const count2 = 0 || 10; // 10 (|| verifica todo el conjunto falsy)# Python no tiene el operador ??# En su lugar, utiliza comparaciones explícitascount = 0result = count if count is not None else 10 # 0El operador de coalescencia nula (Nullish Coalescing) de JavaScript, representado por ??, solo verifica los valores null y undefined, por lo que es seguro cuando 0 o una cadena vacía son valores válidos. Python no tiene un operador equivalente, por lo que se debe escribir explícitamente la comparación is not None.
Combinado con not
# not es una inversión simple, no evaluación de cortocircuitoprint(not True) # Falseprint(not 0) # Trueprint(not "") # Trueprint(not "hello") # False# Prioridad en condiciones compuestas# not > and > orprint(not True or False) # False (not True → False, False or False → False)print(not (True or False)) # False (True or False → True, not True → False)
# Utiliza paréntesis explícitamente para mejorar la legibilidadif (age >= 18) and (not is_banned): print("Access allowed")Resumen clave
| Operador | Condición de evaluación perezosa | Valor devuelto |
|---|---|---|
and | El primer operando es falso (falsy) | El primer valor falso, o el último valor |
or | El primer operando es verdadero (truthy) | El primer valor verdadero, o el último valor |
not | (Sin evaluación perezosa) | Siempre True o False |
Comprender la evaluación perezosa permite lograr tres objetivos: asignación de valores predeterminados (x or default), acceso seguro (items and items[0]) y optimización del rendimiento (colocar las condiciones más rápidas al principio). La mayoría de los lenguajes de programación (JavaScript, Java, C++) siguen las mismas reglas; por lo tanto, una vez que se aprenden, pueden aplicarse en cualquier contexto.