Son estructuras diseñadas para que cierto bloque de ordenes se repita una cantidad determinada de veces o que se ejecuten mientras se cumplen ciertas condiciones. Los Bloques son definidos por "indentation" o sea la sangría. No hay una orden específica para indicar el final, este se asume con la desaparición de la sangría usada en esa estructura de control. Aunque puede continuar otra de una estructura superior a esta. Es decir, cuando más interna es la estructura más larga es la sangría que determina la pertencia de las órdenes.
En las estructuras de control tenemos las siguientes órdenes
- for para realizar bucles o loops.
- if( )/elif/else para hacer preguntas y tomar decisiones.
- while( ) para realizar una tarea mientras el argumento sea verdadero.
- "Comprehension list" que son una versión compacta de los comandos anteriores y sirven para realizar todos los comandos anteriores en un sólo renglón. Sirven para volver más compacto el código.
Empecemos con la sentencia for para realizar "loops" o bucles.
Lo uso haciendo:
for variable in contenedor:
Bloque determinado con una sangría similar que contiene las operaciones a realizar
La variable es similar a como usamos la variable de un DO de Fortran. El contenedor puede ser cualquiera de los que vimos la clase anterior. Es de decir una Lista, Tupla, Set, etc.
Los ":" al final de la línea son obligatorios porque avisan que ahora viene el bloque de órdenes del bucle.
La idea es que la variable va tomando a todos los elementos del contenedor. En muchos textos a los elementos del contenedor se los considera como iterables. Dado que itero sobre todos los elementos de ese conjunto. Por lo cual las operaciones que se especifiquen en el bloque del for serán aplicadas a todos los elementos del contenedor.
También hay que recordar que un contenedor puede tener como un elemento a otro contenedor, así que donde hablamos de variable podría ser algo que englobe más de una variable de las básicas. Por ejemplo, podría tener una lista de tuplas, por lo cual el for actuaría de a una tupla por vez.
for i in [1,2,3]:
print(i+1) # Prestar atención a la sangría
# ("indentation" en inglés)
Recordando que un contenedor puede tener distintos tipos de variables básicas, podríamos hacer lo siguiente:
for cosa in [1,'ff',2]:
print(cosa)
print('end')
print('final end') # El final de la sangría indica el final
# del bloque "for"
Por ejemplo, puedo recorrer un diccionario por sus keys.
# Si defino un diccionario
ATOMIC_MASS = {} # <-- Diccionario vacío
ATOMIC_MASS['H'] = 1
ATOMIC_MASS['He'] = 4
ATOMIC_MASS['C'] = 12
ATOMIC_MASS['N'] = 14
ATOMIC_MASS['O'] = 16
ATOMIC_MASS['Ne'] = 20
ATOMIC_MASS['Ar'] = 40
ATOMIC_MASS['S'] = 32
ATOMIC_MASS['Si'] = 28
ATOMIC_MASS['Fe'] = 55.8
# Puedo imprimir a partir de las keys, todos los valores del diccionario.
# Ojo. la salida no está ordenada
# dict.keys() es el método que lista los "keys" de diccionario
for key in ATOMIC_MASS.keys():
print(key, ATOMIC_MASS[key])
Puedo hacer una preguna al estilo:
if(Condición):
pasa esto si "_condición_" es verdadero
elif(otra Condición):
pasa esto otro si "_otra condición_" es verdera y "algo" fue falso
else:
Como las anteriores fueron falsas, hago esto que sigue acá
Note la sangría que ordena los inicios y finales en cada bloque de actividades.
La Condición puede ser una variable lógica que contiene un verdadero o un falso, o una comparación entre magnitudes realizada por un operador. A estos casos los veremos a continuación.
Se pueden utilizar las sentencias de Control con un estilo bastante parecido al Fortran salvo por la orden que en Fortran se llama "elseif()" y en Python "elif()"
Note que cada orden de la estructura del bloque lleva los ":" al final
En el caso de que requiera comparar dos valores o más, deberá escribir la condición en el IF(), y esta se activará cuando esa condición sea verdadera. Las condiciones para activar o no un IF() tienen en python un muy rico lenguaje para detallar eventos que pueden ser verdaderos o falsos.
Operadores de Comparación: Se comparan números entre ellos, recordar que se puede poner una expresión que será evaluada antes de realizar la comparación.
Operador | Descripcion | sintaxis |
---|---|---|
> | Mayor | X > Y |
< | Menor | X < Y |
== | igual | X == Y |
>= | Mayor o igual | X >= Y |
<= | Menor o igual | X <= Y |
!= | No es igual | X != Y |
Operadores Lógicos:
Operador | Descripcion | sintaxis |
---|---|---|
and | Verdadero si ambos son verdaderos | X and Y |
or | Verdadero si uno es verdadero | X or Y |
not | Verdadero si es falso, y falso si es verdero | not X |
Operadores de indentidad: Verifican que ocupen la misma memoria ram, con otras palabras, se pregunta si son el mismo objeto o parte de él.
Operador | Descripcion | sintaxis |
---|---|---|
is | x es igual a y | X is Y |
is not | x no es igual a y | X is not Y |
En el caso de los diccionarios se usa el operador 'in' para preguntar si una 'key' en particular está definida. El modo de hacer estas operaciones sería el siguiente:
num = 223.4
if num > 0:
print("Es un número positivo")
elif num == 0:
print("Cero")
else:
print("Es un número negativo")
Puede tener estructuras de control una dentro de otra, pero las diferentes sangrías me indican los niveles en que cada estructura es válida.
for i in range(10):
if i > 5:
print(i)
# Prestar atención a la doble sangría del if
for i in range(10):
if i > 5:
print(i)
else:
print(i ,'es menor que cinco')
print('END')
También puedo usar estructuras de control en un SET aunque sus elementos no estén numerados
este_set = {"frutilla", "banana", "cereza"}
for x in este_set:
print(x)
En el caso de los diccionarios se usa el operador 'in' para preguntar si una llave o 'key' en particular está definida. Veamos su modo de uso utilizando el diccionario que definimos anteriormente y preguntemos si 'mapa' nos dispara un resultado (spoiler: no está)
a_diccionario = {'uno' : 1.0,
'dos' : 2.0,
'una_lista' : ['esta', 'es', 'una', 'lista']
}
if 'mapa' in a_diccionario:
print(a_diccionario['mapa'])
else:
print('Esa llave no existe')
También para el caso de un diccionario se puede pedir que la orden for, recorra simultáneamente llaves y valores. Esto se puede hacer porque la función items( ) de los diccionarios crea una tupla (llave, valor)
Esta orden puede ser muy confusa y se tarda en entenderla... <-- tomarlo con calma
print( a_diccionario.items())
Note que se imprimen una lista de tuplas, donde cada tupla es (llave, valor) y si es una lista puedo usar un for, pero descomponiendo la tupla en sus dos componentes por separado (recordar que esto es una propiedad de las tuplas).
for llave, valor in a_diccionario.items():
print(llave,'=', valor)
Estos comandos tienen una estructura perecida a un if()/else, pero diseñada para manejar errores.
Lo que escribo en el bloque del try: es probado y si es cierto ejecutado, pero si no lo es se activa el bloque del except tipo_de_error. Es decir, genera una excepción, avisa de ella y el programa sigue corriendo, no muere como debería haber pasado. Hay una cantidad muy importante de tipos de errores y en cada versión de python se han ido agregando más posibles errores. En la versión de Python que se esté utilizando tendrá un manual con un listado de ellos.
Probémoslo con el diccionario, y en este caso la excepción es 'KeyError'
try:
print(a_diccionario['dos'])
except KeyError:
print('No estaba la llave')
# y si pido una llave inexistente, se activa
# el except
try:
print(a_diccionario['mapa'])
except KeyError:
print('No estaba la llave')
El comando while(algo) funciona creando un loop mientras "algo" sea verdadero. En "algo" podemos construir un condicional con los operadores que hemos visto.
Por ejemplo:
i = 1
while i < 4:
print(i)
i += 1
Podemos incluso poner otras estructuras de control y generar un break que rompe al while dada una condición, veamos como:
i = 1
while i < 6:
print(i)
if i == 3:
break
i += 1
Entonces cuando i sea igual a 3 el loop terminará.
O con el comando continue se podrá evitar que cierto valor sea procesado, provocando que el programa siga con el próximo valor en la orden while().
i = 0
while i < 6:
i += 1
if i == 3:
continue
print(i)
Notar que el valor "3" no fue considerado en la secuencia.
A partir del Python 3.10 se agregaró esta nueva sentencia de control, su uso es similar a clasificar en casos a las variantes de un IF(). Aunque también se podría verla como un sistema de clasificar patrones y con ellos generar diferentes resultados. La orden case es muy popular en varios lenguajes incluyendo Fortran 90/95.
Ejemplo, clasifiquemos errores del resultado de llamar a páginas web:
def httperror(status): match status: case 400: return "Bad request" case 404: return "Not found" case 418: return "I'm a teapot" case : return "Something's wrong with the internet"
En esta función se puede apreciar que diferentes números de error generan diferentes textos de salida. Hay que notar que la última pregunta con la orden case (_:) actúa como el equivalente a un "else" de un IF() bloque, es decir, se activa cuando ninguno de los casos anteriores es válido. También se puede preguntar por varios casos en una sóla orden:
status = 404
match status:
case 401 | 403 | 404:
print("No funcionó")
También se pueden hacer cosas más complicadas como utilizar de entrada una tupla con varias componentes. La sentencia match tiene la capacidad de comparar componente a componente de la tupla. En este ejemplo, tomado del tutorial de Guido Van Rossum, no sólo se comparan las componentes si no que se las copia al resultado. Indicando que componente del par ordenado ocupan.
def prueba_match(point):
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origen")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("No es un punto")
prueba_match((0,0))
prueba_match((0,4))
prueba_match((36,0))
prueba_match((3,4))
prueba_match(8)
Estas son formas de estructuras de control extremamadamente compactas y bastante (¡o muy!) confusas cuando se las ve por primera vez. Se caracterizan por ahorrar muchas líneas de código.
Se basan en la idea de "iterar" sobre una estructura de control para generar sus valores, o bien, por ejemplo, convertir una lista en otra (por un cálculo, o un filtrado de sus elementos). Una variante de estas son las funciones generadoras que veremos en un capítulo siguiente.
Veamos un ejemplo de cómo funcionan:
# creamos unas listas
animales = ['perro','gato','loro', 'tortuga','paloma']
números = [1,2,3,4,5]
# y las procesamos
# Por ejemplo, creo una nueva lista en la que sólo estén
# los animales que tengan una "a" en el nombre
nuevos_animales = [x for x in animales if "a" in x]
print("Nueva lista:",nuevos_animales)
# o calculo los cubos de los números de la lista "números"
nuevos_números= [x**3 for x in números]
print("Nuevos Números:", nuevos_números)
La sintaxis más general de una "list comprehension" es:
newlist = [expresión for item in colección if condición == True]
que en realidad se puede reducir a:
newlist = [expresión for item in colección]
Pero existe mucha libertad de cómo usar esta orden, por ejemplo podría haber más de un if() o más de una "collection de items"
newlist = [expresión for item in colección if test1 and test2]
o
newlist = [expresión for item in colección1 and item2 in colección2]
Por colección nos referimos a un iterable, y la definición exacta de un iterable la veremos más adelante, pero por ejemplo las listas, tuplas, etc., son iterables. Es decir puedo acceder uno por uno en un conjunto de elementos.
Note que la definición de un List Comprehensions va rodeada de [] (corchetes) como lo hace la definición de una lista en Python
Por ejemplo, puedo recorrer una lista haciendo:
lista_nueva = [x for x in range(10)]
print(lista_nueva)
O recorrerla poniendo una condición:
lista_nueva = [x for x in range(10) if x < 5]
print(lista_nueva)
O incluso aprovechar los métodos o funciones de los objetos:
# la función upper pasa la letra a mayúscula
newlist = [x.upper() for x in animales]
print(newlist)
Al principio, estas listas de compresión son confusas, pero esta estructura puede entenderse mejor si se la piensa dividida en renglones donde cada uno de ellos desarrolla una actividad. Por ejemplo, si tengo
[x**2 for x in range(10) if x<5]
Expresión que puedo dividir en 3 líneas:
En este ejemplo:
${\bf x^2}$ es la expresión matemática
for x in range(10) es la iteración
if x < 5 es la condición
Separando en las 3 partes es más fácil de entender.
[x**2 # expresión matemática
for x in range(10) # iteración
if x < 5] # condición