Introducción a la programación en Python
clase 3

Aparte de los tipos numéricos, hay en Python otros tipos compuestos, como ser secuencias, sets y mapeos.
Dentro de las secuencias, ya vimos las cadenas de caracteres (strings).
Otro tipo muy importante de secuencia son las listas.

Listas I

Una lista en Python es una estructura de datos formada por una secuencia ordenada de objetos.
Una forma básica de definirlas es entre corchetes, separando sus elementos con comas.

In [1]:
lista_1 = [17, "hola", [23, 12], 'foo']

Las listas en Python son:

  • heterogéneas: pueden estar conformadas por elementos de distintos tipo, incluidos otras listas.
  • mutables: sus elementos pueden modificarse

Los elementos de una lista pueden accederse mediante su índice, siendo 0 el índice del primer elemento.

In [2]:
lista_1[0]
Out[2]:
17

La función len() devuelve la longitud de la lista (su cantidad de elementos).

In [3]:
len(lista_1)
Out[3]:
4

Los índices de una lista van entonces de 0 a len(lista) - 1

In [4]:
lista_1[3]
Out[4]:
'foo'

Pueden usarse también índices negativos, siendo -1 el índice del último elemento.

In [5]:
lista_1[-1]
Out[5]:
'foo'

Los índices negativos van entonces de -1 (último elemento) a -len(lista) (primer elemento).

In [6]:
lista_1[-len(lista_1)]
Out[6]:
17

A través de los índices, pueden cambiarse los elementos de una lista en el lugar.

In [7]:
lista_1[1] = "adiós"
In [8]:
lista_1
Out[8]:
[17, 'adiós', [23, 12], 'foo']

Programación estructurada

La programación estructurada es un paradigma de programación basado en utilizar funciones o subrutinas, y únicamente tres estructuras de control:

  • secuencia: ejecución de una instrucción tras otra.
  • selección o condicional: ejecución de una instrucción o conjunto de instrucciones, según el valor de una variable booleana.
  • iteración (ciclo o bucle): ejecución de una instrucción o conjunto de instrucciones, mientras una variable booleana sea verdadera.

La programación estructurada se fundamente en el teorema correspondiente, que establece que toda función computable puede ser implementada en un lenguaje de programación que combine sólo estas tres estructuras lógicas o de control.

La estructura de secuencia es la que se da naturalmente en el lenguaje, ya que por defecto las sentencias son ejecutadas en el orden en que aparecen escritas en el programa.

Para las estructuras condicionales o de selección, Python dispone de la instrucción if, que puede combinarse con instrucciones elif y/o else.

Para los bucles o iteraciones existen las estructuras while y for.

if

La instrucción if verifica el valor de verdad de una expresión, y si es verdadera ejecuta el código correspondiente.
La sintaxis es:

if condición:
    bloque de código (indentado)

La condición debe ser cualquier expresión que devuelva el valor True o False.
Por ejemplo, pueden usarse cualquiera de los operadores de comparación vistos en la clase 1.

In [9]:
a = 5
if a > 0:
    print("a es positivo")
a es positivo

Las comparaciones pueden combinarse con los operadores booleanos and, or y not.

In [10]:
a = 5
if a >= 0 and a < 10:
    print("a es un número entre 0 y 9")
a es un número entre 0 y 9

Ejemplo 3.1

Vamos a definir una función que, dada una altura en número de nota MIDI, verfica que se encuentra dentro de un rango determinado (por defecto, el rango completo de notas MIDI, 0 a 127).

In [11]:
def nota_en_rango(nota, lim_inf=0, lim_sup=127):
    """Verifica si una altura en nota MIDI está dentro de un rango"""
    
    if nota < lim_inf or nota > lim_sup: # imprime una advertencia si la nota está fuera de rango.
        print("ATENCIÓN: la nota {} está fuera del rango {} - {}".format(nota, lim_inf, lim_sup))
        

nota = 88
nota_en_rango(nota)
nota_en_rango(nota, 55, 80)
ATENCIÓN: la nota 88 está fuera del rango 55 - 80

if - else

La instrucción if puede opcionalmente combinarse con la instrucción else, que ejecuta un bloque de código alternativo, cuando no se cumple la condición del if.

Por ejemplo, la función anterior solamente imprime una advertencia cuando la altura está fuera de rango; en caso contrario, pasa silenciosamente. Podemos completar la estructura if con un else, para que la función reporte un resultado en todos los casos.

Ejemplo 3.1b

In [12]:
def nota_en_rango(nota, lim_inf=0, lim_sup=127):
    """Verifica si una altura en nota MIDI está dentro de un rango"""
    
    if nota < lim_inf or nota > lim_sup: # imprime una advertencia si la nota está fuera de rango.
        print("ATENCIÓN: la nota {} está fuera del rango {} - {}".format(nota, lim_inf, lim_sup))
    else: # en caso contrario, devuelve un OK
        print("nota {}: OK".format(nota))
        

nota = 88
nota_en_rango(nota)
nota_en_rango(nota, 55, 80)
nota 88: OK
ATENCIÓN: la nota 88 está fuera del rango 55 - 80

if - elif

Las estructuras condicionales if también pueden combinarse con una o más instrucciones elif, que es una abreviación de else, if.
Cada elif agrega una nueva condición a probar, si no se cumple la condición del if o elif anterior.

La función del ejemplo anterior se puede ampliar para que primero chequee si la nota está por debajo del límite inferior, y en caso contrario, ver si excede el límite superior. De no cumplirse ninguna de las dos condiciones, se ejecuta el else.

Ejemplo 3.1c

In [13]:
def nota_en_rango(nota, lim_inf=0, lim_sup=127):
    """Verifica si una altura en nota MIDI está dentro de un rango"""
    
    if nota < lim_inf: # imprime una advertencia si está debajo del límite inferior
        print("ATENCIÓN: la nota {} está por debajo del límite inferior".format(nota))
    elif nota > lim_sup: # imprime una advertencia si encima del límite superior
        print("ATENCIÓN: la nota {} está por encima del límite superior".format(nota))
    else: # en caso contrario, devuelve un OK
        print("nota {}: OK".format(nota))
        

nota1 = 88
nota2 = -3
nota3 = 132
nota_en_rango(nota1)
nota_en_rango(nota2)
nota_en_rango(nota3)
nota 88: OK
ATENCIÓN: la nota -3 está por debajo del límite inferior
ATENCIÓN: la nota 132 está por encima del límite superior

El esquema de una estructura completa con un if, uno o más elif, y un else, sería la siguiente:

if condición1:
    bloque de código 1 (indentado)
elif condición2:
    bloque de código 2 (indentado)
·
·
·
else:
    bloque de código N (indentado)

while

La instrucción while permite implementar un ciclo o bucle: verifica el valor de una condición, y si ésta es verdadera, ejecuta el código que sigue. El proceso se repite hasta que la expresión condicional sea falsa.

El esquema de la sintaxis es:

while condición:
    bloque de código (indentado)
In [14]:
contador = 0
while contador <= 5:
    print(contador, end=' ')
    contador +=1    # se incrementa el valor de la variable
0 1 2 3 4 5 

Es importante asegurarse que en algún momento la expresión condicional se vuelva falsa, de lo contrario el programa quedará estancado en un bucle sin fin. En el ejemplo anterior tomamos esa precaución, incrementado el valor de la variable contador en cada pasada.

asignación aumentada

Es frecuente que una variable tenga que ser redefinida en función de sí misma, como en el ejemplo anterior, donde a la variable contador se le sumaba 1 en cada pasada. En vez de escribir

contador = contador + 1

se puede abreviar a su equivalente

contador += 1

que no sólo es más corto de escribir, sino también más eficiente.

Existe en Python todo un conjunto de operadores de este tipo, llamados de asignación aumentada, entre los que podemos mencionar:

X += Y    ( X = X + Y )
X −= Y    ( X = X - Y )
X *= Y    ( X = X * Y )
X **= Y   ( X = X ** Y )
X /= Y    ( X = X / Y )
X //= Y   ( X = X // Y )
X %= Y    ( X = X % Y )

while - else

Al igual que la estructura if, la estructura while también puede combinarse con una instrucción else. El bloque de código a continuación de la instrucción else se ejecutará cuando la expresión condicional del while sea falsa.

El nombre de la instrucción es equívoco, ya que el bloque del else se ejecutará en todos los casos; tiene la ventaja de mantener el mismo nombre y la misma sintaxis que en las demás estructuras de control.

En el ejemplo anterior podemos agregar una instrucción else al final, que ejecutará el código siguiente cuando la condición del while sea falsa.

In [15]:
contador = 0
while contador <= 5:
    print(contador, end=' ')
    contador +=1    # se incrementa el valor de la variable
else:
    print("\nno se ejecuta el while, el contador es", contador)
0 1 2 3 4 5 
no se ejecuta el while, el contador es 6

Ejemplo 3.2

In [16]:
def fib(n):
    """Imprime una serie de Fibonacci hasta el número n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()    # imprime un salto de línea cuando termina

fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 

for

La instrucción for implementa un bucle, recorriendo ordenadamente una secuencia, o cualquier otro objeto iterable.

La sintaxis básica es la siguiente:

for i in s:
    bloque de código
  • s puede ser cualquier objeto iterable, como una lista, una cadena de caracteres, u otros.
  • cada ítem de s es asignado, por orden, a la variable i, y se ejecuta el bloque de código.
  • es habitual que el bloque de código utilice la variable i, con diferente resultado para cada ítem del objeto s.
  • la estructura for concluye cuando se termina de recorrer todos los elementos de s.
In [17]:
for item in [9, 7, 13, 22]:
    print(item)
9
7
13
22

In [18]:
for letra in "hola":
    print(letra*5, end='')
hhhhhooooolllllaaaaa

for - else

Al igual que en las estructuras de bucle creadas con la instrucción while, acá se puede utilizar la instrucción else para ejecutar un bloque de código una vez que se haya terminado la iteración.

In [19]:
frutas_en_la_heladera = ['una manzana', 'dos naranjas', 'un durazno']
for i in frutas_en_la_heladera:
    print("como", i)
else:
    print("se terminaron las frutas...")    
como una manzana
como dos naranjas
como un durazno
se terminaron las frutas...

range()

En las estructuras for es frecuente utilizar la función range(), que genera una secuencia de valores enteros entre un valor inicial y un límite superior (sin uncluirlo), con un determinado incremento.

Su sintaxis completa es:

range(valor_inicial, limite_superior, incremento)
  • por defecto, el incremento es 1.
  • si se pasa un solo argumento, se considera como el límite superior, siendo el valor inicial cero.

Por lo tanto, range(10) equivale a range(0, 10, 1).

In [20]:
for i in range(5): # de 0 a 5 (excluyente), con incremento 1
    print(i, end=' ')
0 1 2 3 4 
In [21]:
for i in range(5,10): # de 5 a 10 (excluyente), con incremento 1
    print(i, end=' ')
5 6 7 8 9 
In [22]:
for i in range(0,20,5): # de 0 a 20 (excluyente), con incremento 5
    print(i, end=' ')
0 5 10 15 

list()

Aunque tiene la forma de una función, list() es un constructor, que crea una lista a partir de un objeto iterable.
Si el objeto ya es una lista, simplemente se crea una copia.
Pero puede ser otro tipo de secuencia, como una cadena str, o una secuencia generada por la función range().

In [23]:
b = list("¡hola!")
print(b)
['¡', 'h', 'o', 'l', 'a', '!']

In [24]:
a = list(range(5, -5, -1))
print(a)
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]


Ejercicio 3.1

Ampliar el programa del Ejercicio 1.1 (clase 1), de modo de que calcule el factor de multiplicación y la frecuencia resultante para todos los grados de la escala.

Para cada grado se debe imprimir en pantalla:

  • el número de grado (empezando en 0)
  • el factor de multiplicación, redondeado a cuatro dígitos después de la coma
  • el valor en frecuencia, redondeado a dos dígitos después de la coma

Las tres columnas deben estar alineadas por tabuladores.

Ejemplo de ejecución del programa, para división en 12 de la octava, frecuencia de referencia 440:

ingrese la cantidad de grados a dividir la "octava": 12
ingrese la relación de frecuencia correspondiente a la "octava": 2
ingrese la frecuencia base en Hz: 440

cociente:  1.0594630943592953

0   1.0000  440.00
1   1.0595  466.16
2   1.1225  493.88
3   1.1892  523.25
4   1.2599  554.37
5   1.3348  587.33
6   1.4142  622.25
7   1.4983  659.26
8   1.5874  698.46
9   1.6818  739.99
10  1.7818  783.99
11  1.8877  830.61
12  2.0000  880.00

Ejercicio 3.2

Escribir un programa que, partiendo del armónico 8, calcule el factor de multiplicación hasta el armónico 16.

El programa debe preguntar una frecuencia de referencia (que se asociará al armónico 8), y para cada armónico se debe imprimir en pantalla:

  • el número de armónico
  • el factor de multiplicación, redondeado a tres dígitos después de la coma
  • el valor en frecuencia, redondeado a un dígito después de la coma

Las tres columnas deben estar alineadas por tabuladores.

Ejemplo de ejecución del programa, para frecuencia de referencia 440:

ingrese la frecuencia base en Hz: 440

8   1.000   440.0
9   1.125   495.0
10  1.250   550.0
11  1.375   605.0
12  1.500   660.0
13  1.625   715.0
14  1.750   770.0
15  1.875   825.0
16  2.000   880.0