Módulo 1 · Lección 12 de 59
Operadores lógicos
Combinar varias condiciones con and, or y not, la prioridad entre ellos y por qué la evaluación de cortocircuito te salva de errores.
Una condición sola rara vez basta. Para entrar en el descuento hay que ser socio y haber gastado más de 50 euros. El formulario es inválido si falta el nombre o falta el correo. Y a veces lo que quieres es justo lo contrario de lo que sabes comprobar.
Python tiene tres operadores para eso, y en lugar de los símbolos raros de otros lenguajes usa tres palabras en inglés que se leen casi como una frase.
and: las dos cosas#
and da True solo cuando ambos lados son verdaderos:
| Izquierda | Derecha | and |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
edad = 25
tiene_carnet = True
print(edad >= 18 and tiene_carnet)Trueor: al menos una#
or da True cuando alguno de los dos lados es verdadero:
| Izquierda | Derecha | or |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
dia = "sábado"
festivo = False
print(dia == "sábado" or dia == "domingo" or festivo)TrueSe pueden encadenar tantos or como quieras, como en ese ejemplo con tres. Lo mismo vale para and.
not: lo contrario#
not es el único que trabaja con un solo valor: le da la vuelta.
| Valor | not |
|---|---|
True | False |
False | True |
nota = 4.5
print(not nota >= 5)TrueEse ejemplo, sin embargo, es justo el mal uso de not. not nota >= 5 es una forma retorcida de escribir nota < 5. Reserva not para cuando no exista un operador directo:
respuesta = " "
print(not respuesta.strip())TrueAhí sí se lee bien: "si no hay respuesta".
Prioridad#
Cuando mezclas operadores en la misma expresión, Python aplica este orden:
| Prioridad | Operadores |
|---|---|
| 1 (primero) | Los aritméticos: **, *, /, //, %, +, - |
| 2 | Las comparaciones: ==, !=, <, >, <=, >= |
| 3 | not |
| 4 | and |
| 5 (último) | or |
De ahí salen dos consecuencias prácticas. La primera es que las comparaciones se calculan antes que los operadores lógicos, así que no necesitas paréntesis alrededor de cada una:
if edad >= 18 and nota >= 5: # correcto y legible
if (edad >= 18) and (nota >= 5): # significa lo mismo, con ruido de másLa segunda es que and se calcula antes que or, lo cual sí puede cambiar el resultado:
print(True or False and False)
print((True or False) and False)True
FalseEn la primera línea Python calcula primero False and False, que es False, y luego True or False, que es True. En la segunda los paréntesis fuerzan el orden contrario. Es exactamente el mismo fenómeno que 2 + 3 * 4: and es la multiplicación y or es la suma de la lógica.
edad = 30
socio = False
print(edad < 18 or edad > 65 and socio)
print((edad < 18 or edad > 65) and socio)False
FalseAquí las dos dan lo mismo por casualidad, con estos datos concretos. Cambia socio a True y edad a 70 y verás que la primera da True y la segunda también; prueba con edad = 10 y socio = False y se separan. Ese "depende de los datos" es precisamente el problema:
Evaluación de cortocircuito#
Python es perezoso con and y or: deja de evaluar en cuanto sabe el resultado.
- En
a and b, siaes falso el resultado ya es falso, así quebno se ejecuta nunca. - En
a or b, siaes verdadero el resultado ya es verdadero, así quebno se ejecuta.
Esto no es un detalle de eficiencia, es una herramienta. Te permite comprobar primero que algo es seguro y solo después usarlo:
entrada = "hola"
print(entrada.isdigit() and int(entrada) > 100)Falseentrada.isdigit() es False, así que Python se detiene ahí y nunca ejecuta int("hola"), que habría dado un ValueError. Compruébalo quitando la protección:
entrada = "hola"
print(int(entrada) > 100)ValueError: invalid literal for int() with base 10: 'hola'El mismo patrón sirve para no pasarse del final de una cadena:
linea = ""
resultado = linea and linea[0] == "#"
print(f"{resultado=}")
linea = "# comentario"
resultado = linea and linea[0] == "#"
print(f"{resultado=}")resultado=''
resultado=TrueCon la cadena vacía, que es falsa, Python se detiene en el primer operando y linea[0] no llega a ejecutarse; el resultado es la propia cadena vacía, que en un if cuenta como falso. Sin la protección delante, el mismo código se cae:
linea = ""
print(linea[0] == "#")IndexError: string index out of rangeY para no dividir entre cero:
divisor = 0
print(divisor != 0 and 100 / divisor > 10)FalseCon esto ya puedes escribir un validador de una sola línea:
edad_texto = input("Edad: ").strip()
if edad_texto.isdigit() and 18 <= int(edad_texto) <= 120:
print(f"Edad registrada: {int(edad_texto)}")
else:
print("Edad no válida: escribe un número entero entre 18 y 120.")Edad: treinta
Edad no válida: escribe un número entero entre 18 y 120.and y or no devuelven True o False#
Mira con atención la salida de esto:
print("hola" and "mundo")
print("" or "sin nombre")
print(0 or 10)
print("Erwin" or "anónimo")
print(None and "nunca")mundo
sin nombre
10
Erwin
NoneNo ha salido ni un True ni un False. Lo que devuelven and y or es uno de los dos operandos, el que ha decidido el resultado:
ordevuelve el primero que sea verdadero, y si ninguno lo es, el último.anddevuelve el primero que sea falso, y si ninguno lo es, el último.
En un if no lo notas, porque el if convierte a booleano de todas formas. Pero fuera de un if da un atajo muy usado en Python, el valor por defecto:
nombre = ""
nombre_mostrado = nombre or "Invitado"
print(f"Hola, {nombre_mostrado}")Hola, InvitadoSi nombre tiene contenido se queda con él; si está vacío, coge "Invitado". Una línea en lugar de cuatro de if/else.