9. Combinar condiciones con lógica booleana
En el Capítulo 7, aprendimos sobre valores booleanos y condiciones simples usando operadores de comparación. En el Capítulo 8, usamos estas condiciones para tomar decisiones con sentencias if. Pero los programas del mundo real a menudo necesitan comprobar múltiples condiciones a la vez. ¿Deberíamos conceder acceso si el usuario tiene la contraseña correcta y ha iniciado sesión? ¿Deberíamos mostrar una advertencia si la temperatura es demasiado alta o demasiado baja? ¿Deberíamos continuar si el archivo no está vacío?
Python proporciona tres operadores lógicos que nos permiten combinar y modificar valores booleanos: and, or y not. Estos operadores son los bloques de construcción para expresar una lógica de toma de decisiones compleja en tus programas.
9.1) Operadores lógicos and, or y not
Los tres operadores lógicos trabajan con valores booleanos (o valores que pueden tratarse como booleanos) para producir nuevos resultados booleanos.
9.1.1) El operador and
El operador and devuelve True solo cuando ambos operandos son verdaderos. Si cualquiera de los operandos es falso, toda la expresión es falsa.
# Ambas condiciones deben ser verdaderas
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# Si cualquiera de las condiciones es falsa, el resultado es False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: FalsePiensa en and como un guardián estricto: todas las condiciones deben cumplirse para que la comprobación general tenga éxito.
Tabla de verdad para and:
| Operando izquierdo | Operando derecho | Resultado |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) El operador or
El operador or devuelve True cuando al menos uno de los operandos es verdadero. Solo devuelve False cuando ambos operandos son falsos.
# Al menos una condición debe ser verdadera
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# Ambas condiciones falsas
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: FalsePiensa en or como un guardián permisivo: solo necesitas satisfacer una condición para pasar.
Tabla de verdad para or:
| Operando izquierdo | Operando derecho | Resultado |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
Aquí tienes un ejemplo práctico para un sistema de elegibilidad de descuento:
# El cliente obtiene descuento si es estudiante O si es una persona mayor
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# Otro cliente
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: FalseEl primer cliente califica porque cumple uno de los criterios (persona mayor), aunque no sea estudiante.
9.1.3) El operador not
El operador not es un operador unario (funciona sobre un único operando) que invierte un valor booleano. Convierte True en False y False en True.
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: FalseTabla de verdad para not:
| Operando | Resultado |
|---|---|
True | False |
False | True |
El operador not es particularmente útil cuando quieres comprobar lo contrario de una condición:
# Comprobar si un archivo NO está vacío
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# Comprobar si el usuario NO ha iniciado sesión
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) Combinar múltiples operadores lógicos
Puedes combinar múltiples operadores lógicos en una sola expresión para construir condiciones más sofisticadas:
# Tienda online: envío gratis si el pedido supera $50 O el cliente es miembro premium
# Y los artículos están en stock
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: TrueSigamos el rastro de esta evaluación:
order_total >= 50se evalúa comoFalse(45.00 no es >= 50)is_premiumesTrueFalse or Truese evalúa comoTruein_stockesTrueTrue and Truese evalúa comoTrue
Aquí tienes otro ejemplo con control de acceso:
# El usuario puede acceder al panel de admin si es admin
# Y (está en la red interna O está usando VPN)
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: TrueFíjate en los paréntesis alrededor de (on_internal_network or using_vpn). Son importantes porque controlan el orden de evaluación, igual que los paréntesis en las expresiones aritméticas.
9.2) Precedencia de operadores en expresiones booleanas (orden Not, And, Or)
Cuando combinas múltiples operadores lógicos sin paréntesis, Python sigue reglas específicas de precedencia para determinar el orden de evaluación. Entender estas reglas te ayuda a escribir condiciones correctas y evitar errores sutiles.
9.2.1) La jerarquía de precedencia
Python evalúa los operadores lógicos en este orden (de mayor a menor precedencia):
not(máxima precedencia)and(precedencia intermedia)or(mínima precedencia)
Esto significa que not se evalúa primero, luego and y, por último, or.
# Sin paréntesis, la precedencia determina el orden
result = True or False and False
print(result) # Output: True
# Cómo evalúa Python esto:
# Paso 1: False and False → False (and tiene mayor precedencia que or)
# Paso 2: True or False → TrueVeámoslo paso a paso con un ejemplo más detallado:
is_weekend = False
is_holiday = True
has_work = True
# Expresión: not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# Orden de evaluación:
# Paso 1: not has_work → not True → False
# Paso 2: is_weekend and is_holiday → False and True → False
# Paso 3: False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) Usar paréntesis para mayor claridad
Incluso cuando entiendes las reglas de precedencia, usar paréntesis hace tu código más claro y evita errores. Los paréntesis anulan la precedencia predeterminada y hacen explícitas tus intenciones.
# Ambiguo sin paréntesis
result = True or False and False
print(result) # Output: True
# Claro con paréntesis - ¿qué queríamos decir realmente?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: True¡Estas dos expresiones producen resultados diferentes! Los paréntesis cambian por completo el significado.
9.2.3) Operadores de comparación y operadores lógicos juntos
Los operadores de comparación (como <, >, ==, !=) tienen mayor precedencia que los operadores lógicos. Esto significa que las comparaciones se evalúan antes que las operaciones lógicas.
age = 25
income = 50000
# No se necesitan paréntesis alrededor de las comparaciones
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# Python lo evalúa como:
# Paso 1: age >= 18 → True
# Paso 2: income >= 30000 → True
# Paso 3: True and True → True9.3) Evaluación de cortocircuito (short-circuit)
Python utiliza evaluación de cortocircuito (short-circuit) al evaluar expresiones booleanas con and y or. Esto significa que Python deja de evaluar en cuanto conoce el resultado final, omitiendo potencialmente la evaluación de operandos posteriores. Este comportamiento es tanto una optimización de rendimiento como una técnica de programación útil.
9.3.1) Cómo hace cortocircuito and
Con el operador and, si el operando izquierdo es False, Python sabe que toda la expresión debe ser False (porque ambos operandos deben ser verdaderos para que and devuelva True). Por lo tanto, Python no evalúa el operando derecho en absoluto.
# Demostración simple
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Evaluación de Python:
# Paso 1: x < 3 → 5 < 3 → False
# Paso 2: Como el lado izquierdo es False, no se evalúa x > 10
# Paso 3: Devolver FalseAquí tienes un ejemplo práctico que muestra por qué la evaluación de cortocircuito importa:
# Comprobar si un número es divisible: evitar la división entre cero
numerator = 100
denominator = 0
# Esto es seguro gracias a la evaluación de cortocircuito
# Si denominator es 0, la división nunca sucede
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# Sin evaluación de cortocircuito, esto causaría un error:
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!La expresión denominator != 0 se evalúa como False, así que Python nunca evalúa numerator % denominator, lo cual causaría un error de división entre cero.
Veamos otro ejemplo con operaciones de cadenas:
# Comprobar propiedades de una cadena de forma segura
text = ""
# Comprobar si text no está vacío Y el primer carácter está en mayúscula
# Seguro porque si text está vacío, nunca intentamos acceder a text[0]
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# Si intentáramos esto sin la comprobación de longitud:
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) Cómo hace cortocircuito or
Con el operador or, si el operando izquierdo es True, Python sabe que toda la expresión debe ser True (porque basta con que al menos uno de los operandos sea verdadero). Por lo tanto, Python no evalúa el operando derecho.
# Demostración simple
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Evaluación de Python:
# Paso 1: x > 10 → 15 > 10 → True
# Paso 2: Como el lado izquierdo es True, no se evalúa x < 5
# Paso 3: Devolver True9.3.3) Aplicaciones prácticas de la evaluación de cortocircuito
Evitar errores:
# Acceder a elementos de una lista de forma segura
numbers = [1, 2, 3]
index = 5
# Comprobar si index es válido antes de acceder
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# Sin cortocircuito, esto se rompería:
# is_valid = numbers[index] > 0 # IndexError!Comprobar múltiples condiciones de manera eficiente:
# Validación de formularios: parar en el primer error
email = "user@example.com"
password = "pass"
age = 25
# Comprobar cada requisito en el orden más probable de fallar
valid_form = (
len(email) > 0 and # Comprobación rápida
"@" in email and # Comprobación rápida
len(password) >= 8 and # Comprobación rápida
age >= 18 # Comprobación rápida
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# Se detiene en la comprobación de longitud de password, no evalúa age9.4) Qué devuelven los operadores and y or con operandos no booleanos, y errores comunes en expresiones booleanas
Hasta ahora, hemos visto and, or y not funcionar con valores booleanos. Pero los operadores lógicos de Python tienen un comportamiento interesante: pueden trabajar con cualquier valor, no solo con True y False. Entender este comportamiento te ayuda a escribir código más conciso y evitar errores comunes.
9.4.1) Comprender la veracidad y falsedad (truthy/falsy) (repaso)
Como aprendimos en el Capítulo 7, Python trata muchos valores no booleanos como “truthy” o “falsy” en contextos booleanos:
Valores falsy (se tratan como False):
FalseNone0(cero de cualquier tipo numérico)""(cadena vacía)[](lista vacía){}(diccionario vacío)()(tupla vacía)
Valores truthy (se tratan como True):
True- Cualquier número distinto de cero
- Cualquier cadena no vacía
- Cualquier colección no vacía
# Demostrar truthiness
if "hello":
print("Non-empty strings are truthy") # Output: Non-empty strings are truthy
if 0:
print("This won't print") # Cero es falsy
else:
print("Zero is falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Non-empty lists are truthy") # Output: Non-empty lists are truthy9.4.2) Qué devuelve realmente and
El operador and no siempre devuelve True o False. En su lugar, devuelve uno de sus operandos:
- Si el operando izquierdo es falsy,
anddevuelve el operando izquierdo (sin evaluar el derecho) - Si el operando izquierdo es truthy,
anddevuelve el operando derecho
# and devuelve el primer valor falsy, o el último valor si todos son truthy
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: NoneSigamos el rastro de estos ejemplos:
# Ejemplo 1: Ambos truthy
result = 5 and 10
# Paso 1: 5 es truthy, así que se evalúa el lado derecho
# Paso 2: Devolver el valor del lado derecho: 10
print(result) # Output: 10
# Ejemplo 2: El lado izquierdo es falsy
result = 0 and 10
# Paso 1: 0 es falsy, así que se devuelve inmediatamente
# Paso 2: No se evalúa el lado derecho
print(result) # Output: 0
# Ejemplo 3: Ambas cadenas truthy
result = "hello" and "world"
# Paso 1: "hello" es truthy, así que se evalúa el lado derecho
# Paso 2: Devolver el valor del lado derecho: "world"
print(result) # Output: world9.4.3) Qué devuelve realmente or
De forma similar, el operador or devuelve uno de sus operandos:
- Si el operando izquierdo es truthy,
ordevuelve el operando izquierdo (sin evaluar el derecho) - Si el operando izquierdo es falsy,
ordevuelve el operando derecho
# or devuelve el primer valor truthy, o el último valor si todos son falsy
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0Sigamos el rastro de estos ejemplos:
# Ejemplo 1: El lado izquierdo es truthy
result = 5 or 10
# Paso 1: 5 es truthy, así que se devuelve inmediatamente
# Paso 2: No se evalúa el lado derecho
print(result) # Output: 5
# Ejemplo 2: El lado izquierdo es falsy
result = 0 or 10
# Paso 1: 0 es falsy, así que se evalúa el lado derecho
# Paso 2: Devolver el valor del lado derecho: 10
print(result) # Output: 10
# Ejemplo 3: Ambos falsy
result = None or 0
# Paso 1: None es falsy, así que se evalúa el lado derecho
# Paso 2: Devolver el valor del lado derecho: 0 (aunque también sea falsy)
print(result) # Output: 09.4.4) Usos prácticos de or para valores predeterminados
Un patrón común es usar or para proporcionar valores predeterminados:
# Preferencias de usuario con valores predeterminados
user_theme = "" # El usuario no ha configurado un tema
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# Valores de configuración
max_retries = None # No está configurado
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5Este patrón funciona porque si el lado izquierdo es falsy (cadena vacía, None, 0, etc.), or devuelve el lado derecho (el valor predeterminado).
9.4.12) Resumen de lo que devuelven los operadores
Aquí tienes un resumen completo de lo que devuelve cada operador lógico:
Operador and:
- Devuelve el primer operando falsy
- Si todos los operandos son truthy, devuelve el último operando
- Usa evaluación de cortocircuito (se detiene en el primer valor falsy)
Operador or:
- Devuelve el primer operando truthy
- Si todos los operandos son falsy, devuelve el último operando
- Usa evaluación de cortocircuito (se detiene en el primer valor truthy)
Operador not:
- Siempre devuelve un booleano (
TrueoFalse) notconvierte el operando a booleano y luego lo niega
# Demostrar los tres operadores
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)Comprender estos comportamientos te ayuda a escribir código más conciso y más Pythonic, pero siempre prioriza la claridad. Si usar estas características hace que tu código sea más difícil de entender, es mejor ser explícito.
En este capítulo, hemos explorado cómo combinar condiciones simples en lógica booleana compleja usando los operadores and, or y not de Python. Hemos aprendido sobre la precedencia de operadores, la evaluación de cortocircuito y el comportamiento sorprendente de los operadores lógicos con valores no booleanos. También hemos examinado errores comunes y buenas prácticas para escribir expresiones booleanas claras y correctas.
Estas herramientas te permiten expresar una lógica de toma de decisiones sofisticada en tus programas. Combinadas con las sentencias if del Capítulo 8, ahora puedes manejar prácticamente cualquier lógica condicional que requieran tus programas. En el próximo capítulo, exploraremos las expresiones condicionales, que proporcionan una forma compacta de elegir entre dos valores según una condición.