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.

FOR

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.

In [1]:
for i in [1,2,3]: 
      print(i+1)        # Prestar atención a la sangría 
                        # ("indentation" en inglés)
2
3
4

Recordando que un contenedor puede tener distintos tipos de variables básicas, podríamos hacer lo siguiente:

In [2]:
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"
1
end
ff
end
2
end
final end

Por ejemplo, puedo recorrer un diccionario por sus keys.

In [3]:
# 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])
H 1
He 4
C 12
N 14
O 16
Ne 20
Ar 40
S 32
Si 28
Fe 55.8

IF/ELIF/ELSE

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

Operadores condicionales

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:

In [4]:
num = 223.4

if num > 0:
    print("Es un número positivo")
elif num == 0:
    print("Cero")
else:
    print("Es un número negativo")
    
Es un número positivo

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.

In [5]:
for i in range(10):
    if i > 5:
        print(i)

# Prestar atención a la doble sangría del if
6
7
8
9
In [6]:
for i in range(10):
    if i > 5:
        print(i)
    else:
        print(i ,'es menor que cinco')
print('END')
0 es menor que cinco
1 es menor que cinco
2 es menor que cinco
3 es menor que cinco
4 es menor que cinco
5 es menor que cinco
6
7
8
9
END

También puedo usar estructuras de control en un SET aunque sus elementos no estén numerados

In [7]:
este_set = {"frutilla", "banana", "cereza"}

for x in este_set:
  print(x)
frutilla
banana
cereza

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á)

In [8]:
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')
    
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

In [9]:
print( a_diccionario.items())
dict_items([('uno', 1.0), ('dos', 2.0), ('una_lista', ['esta', 'es', 'una', 'lista'])])

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).

In [10]:
for llave, valor in a_diccionario.items():
    print(llave,'=', valor)
uno = 1.0
dos = 2.0
una_lista = ['esta', 'es', 'una', 'lista']

Try/Except

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'

In [11]:
try:
  print(a_diccionario['dos'])
except KeyError:
  print('No estaba la llave')
2.0
In [12]:
# y si pido una llave inexistente, se activa
# el except

try:
  print(a_diccionario['mapa'])
except KeyError:
  print('No estaba la llave')
No estaba la llave

WHILE( ), Break y Continue

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:

In [13]:
i = 1
while i < 4:
  print(i)
  i += 1
1
2
3

Podemos incluso poner otras estructuras de control y generar un break que rompe al while dada una condición, veamos como:

In [14]:
i = 1
while i < 6:
  print(i)
  if i == 3:
    break
  i += 1
1
2
3

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().

In [15]:
i = 0
while i < 6:
  i += 1
  if i == 3:
    continue
  print(i)
1
2
4
5
6

Notar que el valor "3" no fue considerado en la secuencia.

Sentencias Match/Case

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:

In [16]:
status = 404
match status:
    case 401 | 403 | 404:
        print("No funcionó")
    
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.

In [17]:
        
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)
Origen
Y=4
X=36
X=3, Y=4
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[17], line 19
     17 prueba_match((36,0))
     18 prueba_match((3,4))
---> 19 prueba_match(8)

Cell In[17], line 13, in prueba_match(point)
      1 def prueba_match(point):
      2 # point is an (x, y) tuple
      3     match point:
      4       case (0, 0):
      5         print("Origen")
      6       case (0, y):
      7         print(f"Y={y}")
      8       case (x, 0):
      9         print(f"X={x}")
     10       case (x, y):
     11         print(f"X={x}, Y={y}")
     12       case _:
---> 13         raise ValueError("No es un punto")

ValueError: No es un punto

Listas de Comprensión (List Comprehension)

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:

In [18]:
# 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)
Nueva lista: ['gato', 'tortuga', 'paloma']
Nuevos Números: [1, 8, 27, 64, 125]

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:

In [19]:
lista_nueva = [x for x in range(10)]
print(lista_nueva)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

O recorrerla poniendo una condición:

In [20]:
lista_nueva = [x for x in range(10) if x < 5]
print(lista_nueva)
[0, 1, 2, 3, 4]

O incluso aprovechar los métodos o funciones de los objetos:

In [21]:
# la función upper pasa la letra a mayúscula
newlist = [x.upper() for x in animales]
print(newlist)
['PERRO', 'GATO', 'LORO', 'TORTUGA', 'PALOMA']

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:

  • la expresión matemática,
  • la iteración y
  • la condición para que se ejecute.

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.

In [22]:
[x**2 # expresión matemática
  for x in range(10) # iteración
  if x < 5] # condición
Out[22]:
[0, 1, 4, 9, 16]