Condicionales¶
En esta sección veremos las sentencias if
y match-case
junto a las distintas variantes que pueden asumir, pero antes de eso introduciremos algunas cuestiones generales de escritura de código. [1]
Definición de bloques¶
A diferencia de otros lenguajes que utilizan llaves para definir los bloques de código, cuando Guido Van Rossum creó el lenguaje quiso evitar estos caracteres por considerarlos innecesarios. Es por ello que en Python los bloques de código se definen a través de espacios en blanco, preferiblemente 4. [2] En términos técnicos se habla del tamaño de indentación.
Consejo
Esto puede resultar extraño e incómodo a personas que vienen de otros lenguajes de programación pero desaparece rápido y se siente natural a medida que se escribe código.
Comentarios¶
Los comentarios son anotaciones que podemos incluir en nuestro programa y que nos permiten aclarar ciertos aspectos del código. Estas indicaciones son ignoradas por el intérprete de Python.
Los comentarios se incluyen usando el símbolo almohadilla #
y comprenden hasta el final de la línea.
# Universe age expressed in days
universe_age = 13800 * (10 ** 6) * 365
Los comentarios también pueden aparecer en la misma línea de código, aunque la guía de estilo de Python no aconseja usarlos en demasía:
stock = 0 # Release additional articles
Reglas para escribir buenos comentarios: [6]
Los comentarios no deberían duplicar el código.
Los buenos comentarios no arreglan un código poco claro.
Si no puedes escribir un comentario claro, puede haber un problema en el código.
Los comentarios deberían evitar la confusión, no crearla.
Usa comentarios para explicar código no idiomático.
Proporciona enlaces a la fuente original del código copiado.
Incluye enlaces a referencias externas que sean de ayuda.
Añade comentarios cuando arregles errores.
Usa comentarios para destacar implementaciones incompletas.
Ancho del código¶
Los programas suelen ser más legibles cuando las líneas no son excesivamente largas. La longitud máxima de línea recomendada por la guía de estilo de Python es de 80 caracteres.
Sin embargo, esto genera una cierta polémica hoy en día, ya que los tamaños de pantalla han aumentado y las resoluciones son mucho mayores que hace años. Así las líneas de más de 80 caracteres se siguen visualizando correctamente. Hay personas que son más estrictas en este límite y otras más flexibles.
En caso de que queramos romper una línea de código demasiado larga, tenemos dos opciones:
Usar la barra invertida
\
:>>> factorial = 4 * 3 * 2 * 1 >>> factorial = 4 * \ ... 3 * \ ... 2 * \ ... 1
Usar los paréntesis
(...)
:>>> factorial = 4 * 3 * 2 * 1 >>> factorial = (4 * ... 3 * ... 2 * ... 1)
La sentencia if
¶
La sentencia condicional en Python (al igual que en muchos otros lenguajes de programación) es if
. En su escritura debemos añadir una expresión de comparación terminando con dos puntos al final de la línea. Veamos un ejemplo:
>>> temperature = 40
>>> if temperature > 35:
... print('Aviso por alta temperatura')
...
Aviso por alta temperatura
Nota
Nótese que en Python no es necesario incluir paréntesis (
y )
al escribir condiciones. Hay veces que es recomendable por claridad o por establecer prioridades.
En el caso anterior se puede ver claramente que la condición se cumple y por tanto se ejecuta la instrucción que tenemos dentro del cuerpo de la condición. Pero podría no ser así. Para controlar ese caso existe la sentencia else
. Veamos el mismo ejemplo anterior pero añadiendo esta variante:
>>> temperature = 20
>>> if temperature > 35:
... print('Aviso por alta temperatura')
... else:
... print('Parámetros normales')
...
Parámetros normales
Podríamos tener incluso condiciones dentro de condiciones, lo que se viene a llamar técnicamente condiciones anidadas [3]. Veamos un ejemplo ampliando el caso anterior:
>>> temperature = 28
>>> if temperature < 20:
... if temperature < 10:
... print('Nivel azul')
... else:
... print('Nivel verde')
... else:
... if temperature < 30:
... print('Nivel naranja')
... else:
... print('Nivel rojo')
...
Nivel naranja
Python nos ofrece una mejora en la escritura de condiciones anidadas cuando aparecen consecutivamente un else
y un if
. Podemos sustituirlos por la sentencia elif
:
Apliquemos esta mejora al código del ejemplo anterior:
>>> temperature = 28
>>> if temperature < 20:
... if temperature < 10:
... print('Nivel azul')
... else:
... print('Nivel verde')
... elif temperature < 30:
... print('Nivel naranja')
... else:
... print('Nivel rojo')
...
Nivel naranja
Ejecución paso a paso a través de Python Tutor:
Asignaciones condicionales¶
Supongamos que queremos asignar un nivel de riesgo de incendio en función de la temperatura. En su versión clásica escribiríamos:
>>> temperature = 35
>>> if temperature < 30:
... fire_risk = 'LOW'
... else:
... fire_risk = 'HIGH'
...
>>> fire_risk
'HIGH'
Sin embargo, esto lo podríamos abreviar con una asignación condicional de una única línea:
>>> fire_risk = 'LOW' if temperature < 30 else 'HIGH'
>>> fire_risk
'HIGH'
Operadores de comparación¶
Cuando escribimos condiciones debemos incluir alguna expresión de comparación. Para usar estas expresiones es fundamental conocer los operadores que nos ofrece Python:
Operador |
Símbolo |
---|---|
Igualdad |
|
Desigualdad |
|
Menor que |
|
Menor o igual que |
|
Mayor que |
|
Mayor o igual que |
|
A continuación vamos a ver una serie de ejemplos con expresiones de comparación. Téngase en cuenta que estas expresiones habría que incluirlas dentro de la sentencia condicional en el caso de que quisiéramos tomar una acción concreta:
# Asignación de valor inicial
>>> value = 8
>>> value == 8
True
>>> value != 8
False
>>> value < 12
True
>>> value <= 7
False
>>> value > 4
True
>>> value >= 9
False
Python ofrece la posibilidad de ver si un valor está entre dos límites de manera directa. Así, por ejemplo, para descubrir si x
está entre 4 y 12 haríamos:
>>> 4 <= x <= 12
True
Operadores lógicos¶
- Podemos escribir condiciones más complejas usando los operadores lógicos:
and
or
not
# Asignación de valor inicial
>>> x = 8
>>> x > 4 or x > 12 # True or False
True
>>> x < 4 or x > 12 # False or False
False
>>> x > 4 and x > 12 # True and False
False
>>> x > 4 and x < 12 # True and True
True
>>> not(x != 8) # not False
True
Véanse las tablas de la verdad para cada operador lógico:
Nota
Una expresión de comparación siempre devuelve un valor booleano, es decir
True
oFalse
.El uso de paréntesis, en función del caso, puede aclarar la expresión de comparación.
Ejercicio
pypas: leap-year
Cortocircuito lógico¶
Es interesante comprender que las expresiones lógicas no se evalúan por completo si se dan una serie de circunstancias. Aquí es donde entra el concepto de cortocircuito que no es más que una forma de denominar a este escenario.
Supongamos un ejemplo en el que utilizamos un teléfono móvil que mide la batería por la variable power
de 0 a 100% y la cobertura 4G por la variable signal_4g
de 0 a 100%.
Para poder enviar un mensaje por Telegram necesitamos tener al menos un 25% de batería y al menos un 10% de cobertura:
>>> power = 10
>>> signal_4g = 60
>>> power > 25 and signal_4g > 10
False
Dado que estamos en un and
y la primera condición power > 25
no se cumple, se produce un cortocircuito y no se sigue evaluando el resto de la expresión porque ya se sabe que va a dar False
.
Otro ejemplo. Para poder hacer una llamada VoIP necesitamos tener al menos un 40% de batería o al menos un 30% de cobertura:
>>> power = 50
>>> signal_4g = 20
>>> power > 40 or signal_4g > 30
True
Dado que estamos en un or
y la primera condición power > 40
se cumple, se produce un cortocircuito y no se sigue evaluando el resto de la expresión porque ya se sabe que va a dar True
.
Nota
Si no se produjera un cortocircuito en la evaluación de la expresión, se seguiría comprobando todas las condiciones posteriores hasta llegar al final de la misma.
«Booleanos» en condiciones¶
Cuando queremos preguntar por la veracidad de una determinada variable «booleana» en una condición, la primera aproximación que parece razonable es la siguiente:
>>> is_cold = True
>>> if is_cold == True:
... print('Coge chaqueta')
... else:
... print('Usa camiseta')
...
Coge chaqueta
Pero podemos simplificar esta condición tal que así:
>>> if is_cold:
... print('Coge chaqueta')
... else:
... print('Usa camiseta')
...
Coge chaqueta
Hemos visto una comparación para un valor «booleano» verdadero (True
). En el caso de que la comparación fuera para un valor falso lo haríamos así:
>>> is_cold = False
>>> if not is_cold: # Equivalente a if is_cold == False
... print('Usa camiseta')
... else:
... print('Coge chaqueta')
...
Usa camiseta
De hecho, si lo pensamos, estamos reproduciendo bastante bien el lenguaje natural:
Si hace frío, coge chaqueta.
Si no hace frío, usa camiseta.
Ejercicio
pypas: marvel-akinator
Valor nulo¶
None
es un valor especial de Python que almacena el valor nulo [4]. Veamos cómo se comporta al incorporarlo en condiciones de veracidad:
>>> value = None
>>> if value:
... print('Value has some useful value')
... else:
... # value podría contener None, False (u otro)
... print('Value seems to be void')
...
Value seems to be void
Para distinguir None
de los valores propiamente booleanos, se recomienda el uso del operador is
. Veamos un ejemplo en el que tratamos de averiguar si un valor es nulo:
>>> value = None
>>> if value is None:
... print('Value is clearly None')
... else:
... # value podría contener True, False (u otro)
... print('Value has some useful value')
...
Value is clearly None
De igual forma, podemos usar esta construcción para el caso contrario. La forma «pitónica» de preguntar si algo no es nulo es la siguiente:
>>> value = 99
>>> if value is not None:
... print(f'{value=}')
...
value=99
Cabe preguntarse por qué utilizamos is
en vez del operador ==
al comprobar si un valor es nulo, ya que ambas aproximaciones nos dan el mismo resultado [7]:
>>> value = None
>>> value is None
True
>>> value == None
True
La respuesta es que el operador is
comprueba únicamente si los identificadores (posiciones en memoria) de dos objetos son iguales, mientras que la comparación ==
puede englobar muchas otras acciones. De este hecho se deriva que su ejecución sea mucho más rápida y que se eviten «falsos positivos».
Cuando ejecutamos un programa Python existe una serie de objetos precargados en memoria. Uno de ellos es None
:
>>> id(None)
4314501456
Cualquier variable que igualemos al valor nulo, únicamente será una referencia al mismo objeto None
en memoria:
>>> value = None
>>> id(value)
4314501456
Por lo tanto, ver si un objeto es None
es simplemente comprobar que su identificador coincida con el de None
, que es exactamente el cometido de la función is()
:
>>> id(value) == id(None)
True
>>> value is None
True
Truco
Python carga inicialmente en memoria objetos como True
o False
, pero también los números enteros que van desde el -5 hasta el 256. Se entiende que tiene que ver con optimizaciones a nivel de rendimiento.
Veracidad¶
Cuando trabajamos con expresiones que incorporan valores booleanos, se produce una conversión implícita que transforma los tipos de datos involucrados a valores True
o False
.
Lo primero que debemos entender de cara comprobar la veracidad son los valores que evalúan a falso o evalúan a verdadero.
Veamos las únicas «cosas» que son evaluadas a False
en Python:
>>> bool(False)
False
>>> bool(None)
False
>>> bool(0)
False
>>> bool(0.0)
False
>>> bool('') # cadena vacía
False
>>> bool([]) # lista vacía
False
>>> bool(()) # tupla vacía
False
>>> bool({}) # diccionario vacío
False
>>> bool(set()) # conjunto vacío
False
Importante
El resto de objetos son evaluados a True
en Python.
Veamos algunos ejemplos que son evaluados a True
en Python:
>>> bool('False')
True
>>> bool(' ')
True
>>> bool(1e-10)
True
>>> bool([0])
True
>>> bool('🦆')
True
Asignación lógica¶
Es posible utilizar operadores lógicos en sentencias de asignación sacando partido de las tablas de la verdad que funcionan para estos casos.
Veamos un ejemplo de asignación lógica utilizando el operador or
:
>>> b = 0
>>> c = 5
>>> a = b or c
>>> a
5
En la línea resaltada podemos ver que se está aplicando una expresión lógica, por lo tanto se aplica una conversión implícita de los valores enteros a valores «booleanos». En este sentido el valor 0
se evalúa a falso y el valor 5
se evalúa a verdadero. Como estamos en un or
el resultado será verdadero, que en este caso es el valor 5 asignado finalmente a la variable a
.
Veamos el mismo ejemplo de antes pero utilizando el operador and
:
>>> b = 0
>>> c = 5
>>> a = b and c
>>> a
0
En este caso, como estamos en un and
el resultado será falso, por lo que el valor 0 es asignado finalmente a la variable a
.
Sentencia match-case
¶
Una de las novedades más esperadas (y quizás controvertidas) de Python 3.10 fue el llamado Structural Pattern Matching que introdujo en el lenguaje una nueva sentencia condicional. Ésta se podría asemejar a la sentencia «switch» que ya existe en otros lenguajes de programación.
Comparando valores¶
En su versión más simple, el «pattern matching» permite comparar un valor de entrada con una serie de literales. Algo así como un conjunto de sentencias «if» encadenadas. Veamos esta aproximación mediante un ejemplo:
>>> color = '#FF0000'
>>> match color:
... case '#FF0000':
... print('🔴')
... case '#00FF00':
... print('🟢')
... case '#0000FF':
... print('🔵')
...
🔴
¿Qué ocurre si el valor que comparamos no existe entre las opciones disponibles? Pues en principio, nada, ya que este caso no está cubierto. Si lo queremos controlar, hay que añadir una nueva regla utilizando el subguión _
como patrón:
>>> color = '#AF549B'
>>> match color:
... case '#FF0000':
... print('🔴')
... case '#00FF00':
... print('🟢')
... case '#0000FF':
... print('🔵')
... case _:
... print('Unknown color!')
...
Unknown color!
Ejercicio
pypas: simple-op
Patrones avanzados¶
La sentencia match-case
va mucho más allá de una simple comparación de valores. Con ella podremos deconstruir estructuras de datos, capturar elementos o mapear valores.
Para ejemplificar varias de sus funcionalidades, vamos a partir de una tupla que representará un punto en el plano (2 coordenadas) o en el espacio (3 coordenadas). Lo primero que vamos a hacer es detectar en qué dimensión se encuentra el punto:
>>> point = (2, 5)
>>> match point:
... case (x, y):
... print(f'({x},{y}) is in plane')
... case (x, y, z):
... print(f'({x},{y},{z}) is in space')
...
(2,5) is in plane
>>> point = (3, 1, 7)
>>> match point:
... case (x, y):
... print(f'({x},{y}) is in plane')
... case (x, y, z):
... print(f'({x},{y},{z}) is in space')
...
(3,1,7) is in space
En cualquier caso, esta aproximación permitiría un punto formado por «strings»:
>>> point = ('2', '5')
>>> match point:
... case (x, y):
... print(f'({x},{y}) is in plane')
... case (x, y, z):
... print(f'({x},{y},{z}) is in space')
...
(2,5) is in plane
Por lo tanto, en un siguiente paso, podemos restringir nuestros patrones a valores enteros:
>>> point = ('2', '5')
>>> match point:
... case (int(), int()):
... print(f'{point} is in plane')
... case (int(), int(), int()):
... print(f'{point} is in space')
... case _:
... print('Unknown!')
...
Unknown!
>>> point = (3, 9, 1)
>>> match point:
... case (int(), int()):
... print(f'{point} is in plane')
... case (int(), int(), int()):
... print(f'{point} is in space')
... case _:
... print('Unknown!')
...
(3, 9, 1) is in space
Imaginemos ahora que nos piden calcular la distancia del punto al origen. Debemos tener en cuenta que, a priori, desconocemos si el punto está en el plano o en el espacio:
>>> point = (8, 3, 5)
>>> match point:
... case (int(x), int(y)):
... dist_to_origin = (x ** 2 + y ** 2) ** (1 / 2)
... case (int(x), int(y), int(z)):
... dist_to_origin = (x ** 2 + y ** 2 + z ** 2) ** (1 / 2)
... case _:
... print('Unknown!')
...
>>> dist_to_origin
9.899494936611665
Con este enfoque, nos aseguramos que los puntos de entrada deben tener todas sus coordenadas como valores enteros:
>>> point = ('8', 3, 5) # Nótese el 8 como "string"
>>> match point:
... case (int(x), int(y)):
... dist_to_origin = (x ** 2 + y ** 2) ** (1 / 2)
... case (int(x), int(y), int(z)):
... dist_to_origin = (x ** 2 + y ** 2 + z ** 2) ** (1 / 2)
... case _:
... print('Unknown!')
...
Unknown!
Cambiando de ejemplo, veamos un fragmento de código en el que tenemos que comprobar la estructura de un bloque de autenticación definido mediante un diccionario. Los métodos válidos de autenticación son únicamente dos: bien usando nombre de usuario y contraseña, o bien usando correo electrónico y «token» de acceso. Además, los valores deben venir en formato cadena de texto:
1>>> # Lista de diccionarios
2>>> auths = [
3... {'username': 'sdelquin', 'password': '1234'},
4... {'email': 'sdelquin@gmail.com', 'token': '4321'},
5... {'email': 'test@test.com', 'password': 'ABCD'},
6... {'username': 'sdelquin', 'password': 1234}
7... ]
8
9>>> for auth in auths:
10... print(auth)
11... match auth:
12... case {'username': str(username), 'password': str(password)}:
13... print('Authenticating with username and password')
14... print(f'{username}: {password}')
15... case {'email': str(email), 'token': str(token)}:
16... print('Authenticating with email and token')
17... print(f'{email}: {token}')
18... case _:
19... print('Authenticating method not valid!')
20... print('---')
21...
22{'username': 'sdelquin', 'password': '1234'}
23Authenticating with username and password
24sdelquin: 1234
25---
26{'email': 'sdelquin@gmail.com', 'token': '4321'}
27Authenticating with email and token
28sdelquin@gmail.com: 4321
29---
30{'email': 'test@test.com', 'password': 'ABCD'}
31Authenticating method not valid!
32---
33{'username': 'sdelquin', 'password': 1234}
34Authenticating method not valid!
35---
Cambiando de ejemplo, a continuación veremos un código que nos indica si, dada la edad de una persona, puede beber alcohol:
1>>> age = 21
2
3>>> match age:
4... case 0 | None:
5... print('Not a person')
6... case n if n < 17:
7... print('Nope')
8... case n if n < 22:
9... print('Not in the US')
10... case _:
11... print('Yes')
12...
13Not in the US
En la línea 4 podemos observar el uso del operador OR.
En las líneas 6 y 8 podemos observar el uso de condiciones dando lugar a cláusulas guarda.
Operador morsa¶
A partir de Python 3.8 se incorpora el operador morsa [5] que permite unificar sentencias de asignación dentro de expresiones. Su nombre proviene de la forma que adquiere :=
Supongamos un ejemplo en el que computamos el perímetro de una circunferencia, indicando al usuario que debe incrementarlo siempre y cuando no llegue a un mínimo establecido.
Versión tradicional
>>> radius = 4.25
... perimeter = 2 * 3.14 * radius
... if perimeter < 100:
... print('Increase radius to reach minimum perimeter')
... print('Actual perimeter: ', perimeter)
...
Increase radius to reach minimum perimeter
Actual perimeter: 26.69
Versión con operador morsa
>>> radius = 4.25
... if (perimeter := 2 * 3.14 * radius) < 100:
... print('Increase radius to reach minimum perimeter')
... print('Actual perimeter: ', perimeter)
...
Increase radius to reach minimum perimeter
Actual perimeter: 26.69
Consejo
Como hemos comprobado, el operador morsa permite realizar asignaciones dentro de expresiones, lo que, en muchas ocasiones, permite obtener un código más compacto. Sería conveniente encontrar un equilibrio entre la expresividad y la legibilidad.