Apuntes
Apuntes
Resumen
Estos apuntes son distribuidos como material complementario del
curso Modelado y simulación de sistemas eléctricos de potencia con
automatización en Python, específicamente para el segundo módulo.
Este curso fue organizado por cecacier e impartido en marzo de
2023. Se abarcan generalidades del lenguaje de programación Python,
estructuras de datos, estructuras de control de flujo y funciones. Para
consultas o reporte de errores, por favor escribir a [email protected].
Índice
1 Generalidades 1
1.1 Particularidades del lenguaje y su filosofía . . . . . . . . . . . 1
1.2 Modo interactivo . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Asignaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Edición de archivos y programas completos . . . . . . . . . . 7
1.6 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2 Estructuras de datos 8
2.1 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Más sobre cadenas . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4 Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5 Diccionarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1
4 Funciones y archivos 29
4.1 Funciones con y sin valores de retorno . . . . . . . . . . . . . 29
4.2 Lectura y escritura de archivos . . . . . . . . . . . . . . . . . 32
5 Ejercicios 34
1 Generalidades
1.1 Particularidades del lenguaje y su filosofía
La principal particularidad de Python es su sintaxis expresiva. Considérese
el ejemplo clásico de la la sucesión de Fibonacci. Dados dos términos iniciales
f0 = 0 y f1 = 1, los demás términos son dados por
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
Incluso sin conocer la sintaxis del lenguaje, no es difícil intuir con alta
precisión lo que el código anterior hace. La palabra def inicia la definición
de una función llamada fib. El valor devuelto por la función es n si n ≤ 2
(los términos iniciales son 0 y 1) y fn−1 + fn−2 si n > 2. Es decir, la
implementación es casi una copia literal de la definición matemática. El
resultado de escribir print(fib(6)) es, por cierto, 8.
Además de ser una implementación compacta, el lenguaje impone una
restricción que la vuelve ordenada: las sangrías de cada línea son obligatorias.
Python sabe exactamente dónde empieza y dónde termina la función porque
solo debe identificar las líneas que estén un nivel más hacia la derecha.
Las restricciones impuestas por el propio lenguaje hacen que el código
en Python parezca, en ocasiones, un pseudocódigo compilable. Esta fue una
consideración del lenguaje desde que se le concibió y es una filosofía muy
propia de Python. Más detalles de esta filosofía se encuentran en el llamado
Zen, que se imprime tras escribir import this.
2
1.2 Modo interactivo
En Python, es posible ejecutar instrucciones de dos formas. La primera
es el llamado modo interactivo y es explicada en este apartado; la segunda
son los programas completos y es explicada en el apartado 1.5. En ambos
casos, se puede utilizar el editor Spyder, que viene incluido en la distribución
de Anaconda. Para acceder al modo interactivo, debe abrirse dicho editor y
buscar la ventana llamada Terminal de IPython.
Una vez ahí, es posible interactuar con Python como si de una conversación
se tratase. Es posible, por ejemplo, efectuar operaciones numéricas:1
>>> 21 + 21
42
3
>>> 42
42
>>> 3.141592
3.141592
>>> 9.9e-31
9.9e-31
La longitud de los números que pueden ser almacenados solo está limitada
por la memoria del equipo. Para ingresar un número complejo, se escribe su
parte imaginaria seguida de la letra j.
>>> 1 + 3j
(1+3j)
Los datos numéricos admiten operaciones, las cuales dan lugar, a su vez, a
más datos numéricos. Las operaciones básicas de suma, resta, multiplicación
y división son llevadas a cabo con los operadores y la precedencia usual.
>>> 45 // 7
6
>>> 45 % 7
3
>>> 2**3
8
4
>>> 'Potencia consumida (kW)'
'Potencia consumida (kW)'
>>> "Potencia consumida (kW)"
'Potencia consumida (kW)'
>>> _[4]
's'
>>> True
True
>>> False
False
>>> 1 == 1
True
>>> 1 == 2
False
>>> 'Hola' == 'Ho' + 'la'
True
Una comparación que puede ser llevada a cabo con cadenas pero no con
números es la inclusión, para la que se utiliza la palabra clave in.
5
Otro tipo de comparaciones son las de orden, con las que se puede verificar si
un número es menor que otro o si una cadena precede a otra según el orden
alfabético.
>>> 1 < 2
True
>>> 'b' < 'a'
False
Los datos lógicos pueden ser sometidos a negación, conjunción y disyunción,
con lo que es posible formar funciones booleanas de complejidad arbitraria.
>>> True and False or True
True
1.4 Asignaciones
Si bien es práctico utilizar Python como calculadora, esto no es sostenible
para reutilizar o incluso entender los cálculos en el futuro. En cambio, se
prefiere utilizar asignaciones. Estas son formas de guardar datos (números,
cadenas o datos lógicos, por ahora) en variables. La siguiente instrucción le
asigna el número 1 a una variable x.
>>> x = 1
El símbolo = no significa lo mismo que en las matemáticas, donde indica
que dos objetos son iguales. En cambio, este símbolo es interpretado por
Python así: el valor 1, que se ubica a la derecha y es un literal (o sea,
puede ser ingresado directamente desde el teclado y es un valor explícito), es
asignado a x, una variable nueva que se ubica a la izquierda y que está siendo
introducida en esa línea, quizá por primera vez. Así, x = 1 es diferente de
1 = x. De hecho, esta última instrucción sería inválida sin importar si x ya
fue definida antes, dado que 1 es un literal y no una variable. La ventaja de
las asignaciones puede ser reconocida en las siguientes instrucciones.
>>> base = 10
>>> altura = 2
>>> area = base*altura
>>> area
20
No hace falta explicar cuál era el propósito de este código cuando se escribió:
el propio nombre de las variables sirve como una pequeña documentación.
Los nombres de las variables están sujetos a restricciones:
6
Cuadro 1. Algunas palabras reservadas de Python.
Palabra Función
as Definición de abreviaturas para módulos
break Terminación de bucles
class Definición de clases
continue Movimiento al inicio del bucle
def Definición de funciones
elif Consideración de casos alternativos
else Consideración de casos restantes
for Iteración sobre elementos conocidos
from Importación de módulos
if Consideración de casos
import Importación de módulos
return Devolución de valores en funciones
while Iteración dependiente de una condición
7
1.6 Comentarios
Además de las asignaciones, una forma de documentar el código es utilizar
comentarios. Esto se refiere a dejar cadenas de caracteres en el código fuente
no para que sean ejecutadas por Python, sino para que sean leídas por
personas en el futuro. En Python existen dos formas de dejar comentarios.
La primera es escribir una cadena sin asignársela a otra variable; la segunda,
utilizar el símbolo # . Todos los caracteres a la derecha de este símbolo son
ignorados, tanto si el símbolo aparece al inicio de una línea (el comentario
abarca una línea por sí solo) o si aparece después de otras instrucciones (el
comentario complementa una línea existente). El siguiente código ilustra
ambos usos.
def fib_lessthan(n):
''' Calcular números de Fibonacci menores que n. '''
return sequence
2 Estructuras de datos
Cuando los programas crecen en complejidad, se vuelve necesario alma-
cenar grandes cantidades de datos. Lo correcto en estos casos es utilizar las
llamadas estructuras de datos. Estas son formas de almacenar cantidades
arbitrarias de datos de forma que puedan ser manejados con eficiencia por
una computadora. (También se podría argumentar que un habaco, una ta-
bleta cuneiforme o un quipu de las civilizaciones andinas eran estructuras de
8
Estructuras
Secuenciales No secuenciales
datos, pero este curso considera solo el caso informático.) En esta clase se
cubrieron cuatro estructuras: listas, tuplas, conjuntos y diccionarios.
Antes de estudiar las particularidades de cada una, conviene analizar el
concepto de secuencialidad. Una estructura de datos es llamada secuencial si
preserva el ordenamiento entre sus elementos. Un caso en que el ordenamiento
es importante y debe ser preservado es cuando se tienen series de tiempo,
como mediciones de tensión o frecuencia: es muy importante saber cuál
medición precede a cuál y esto debe reflejarse en la estructura seleccionada
para almacenarlas. Por otro lado, un caso en que el ordenamiento no sería
importante es si se tuviera una lista de inventario en una empresa: lo que
importa es los elementos que están presentes, sin que necesariamente exista
un orden entre ellos. La figura 1 muestra las estructuras predefinidas de
Python, su secuencialidad y ejemplos de definición.
2.1 Listas
Las listas son colecciones ordenadas de datos, análogas a los arrays en
lenguajes como C y Fortran. Si se desea almacenar los cuatro caracteres 'A',
'B', 'C' y 'D' y, además, se los quiere almacenar manteniendo un orden,
entonces se puede definir una lista x.
>>> y = list(range(10))
>>> y
9
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Una vez que se han almacenado datos en una lista, es posible analizarlos
utilizando funciones predefinidas de Python. Para calcular el número de
elementos de la lista, se utiliza la función len().
>>> len(y)
10
>>> sum(y)
45
>>> min(y)
0
>>> max(y)
9
Como las listas son estructuras ordenadas, Python asocia a cada dato un
número natural que es conocido como índice. El índice 0 se asocia al primer
elemento, el índice 1 al segundo elemento y así sucesivamente. Para extraer
los datos en un índice dado, que es el proceso conocido como indexación, se
yuxtapone el nombre de la lista a la izquierda con el índice entre corchetes
(paréntesis cuadrados) a la derecha.
10
Para acceder a intervalos de la lista, conocidos como rebanadas, se pasan dos
subíndices separados por dos puntos (:).
>>> x[1:4]
['y', 't', 'h']
>>> x[2:5]
['t', 'h', 'o']
>>> x
['P', 'y', 't', 'h', 'o', 'n']
>>> x[0:3]
['P', 'y', 't']
>>> y[0:len(x)]
[0, 1, 2, 3, 4, 5]
Como los índices 0 y len(x) son tan utilizados, Python los toma como
los índices por defecto cuando no se pasan el índice de inicio, el de final o
ninguno de los dos.
>>> x[:4]
['P', 'y', 't', 'h']
>>> x[3:]
['h', 'o', 'n']
>>> x[:]
['P', 'y', 't', 'h', 'o', 'n']
11
x[1:4] = [’y’, ’t’, ’h’]
0 1 2 3 4 5 6
’P’ ’y’ ’t’ ’h’ ’o’ ’n’
-6 -5 -4 -3 -2 -1
>>> x[0:len(x):2]
['P', 't', 'o']
>>> x[::2]
['P', 't', 'o']
>>> x = [1, 2, 3]
>>> y = [3, 4, 5]
12
>>> x + y
[1, 2, 3, 3, 4, 5]
Muchas operaciones pueden ser implementadas solo con las rebanadas o
con la unión de listas. Para invertir una lista, simplemente se recorre con un
paso negativo.
>>> x = list(range(10))
>>> x_reversed = x[::-1]
>>> x_reversed
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Para eliminar el último elemento, se obtiene una rebanada desde el delimitador
de apertura (o sea, sin pasar el índice) hasta el índice -1.
>>> x = list(range(10))
>>> x_popped = x[:-1]
>>> x_popped
[0, 1, 2, 3, 4, 5, 6, 7, 8]
Si se quisiera modificar la lista, se escribiría x = x[:-1]. Para quitar el
elemento asociado al índice 3, se unirían dos rebanadas: una con los elementos
a la izquierda del elemento eliminado y otra con los elementos a su derecha.
>>> x = list(range(10))
>>> x[:3] + x[3+1:]
[0, 1, 2, 4, 5, 6, 7, 8, 9]
Si bien los procedimientos anteriores son válidos, descifrar lo que hacen
no siempre es sencillo. En ocasiones, es preferible escribir explícitamente la
operación a la que se somete la lista. Supóngase que se desea agregar un
nuevo elemento al final. Una forma explícita de hacer esto es yuxtaponer un
punto (.) y append() al nombre de la lista.
>>> x = [1, 2, 3]
>>> x.append(5)
>>> x
[1, 2, 3, 5]
Si se desea ordenar la lista de forma descendente, entonces se pasa el argu-
mento reverse=True.
>>> x = [1, 4, 3, 2]
>>> x.sort()
>>> x
[1, 2, 3, 4]
13
Si se desea ordenar la lista de forma descendente, entonces se puede pasar el
argumento reverse=True.
>>> x = [1, 4, 3, 2]
>>> x.sort(reverse=True)
>>> x
[4, 3, 2, 1]
14
Cuadro 2. Métodos aplicables a las listas. Todos se ejecutan escribiendo el
nombre de la lista, seguido por un punto (.) y por el nombre del método.
Véanse como ejemplo los métodos append() y sort(), explicados en el texto.
Método Descripción
append(element) Anexa element al final de la lista
extend(list2) Extiende la lista con los elementos de list2
index(element) Devuelve menor índice que contiene element
insert(ind, element) Inserta element en el índice ind
pop() Remueve y devuelve el último elemento
reverse() Revierte el orden de la lista
remove(element) Elimina la primera ocurrencia de element
sort() Ordena la lista
copy() Devuelve una copia de la lista
count(element) Cuenta el número de apariciones de element
También aplican las reglas con respecto a los valores por defecto y a los pasos
positivos y negativos.
>>> leyenda[::2]
'Ptni k)'
>>> leyenda[::-1]
')Wk( aicnetoP'
Muchas veces se desea convertir una cadena a una lista o viceversa. Para
hacer lo primero, se utiliza la función list(), que devuelve una lista con
caracteres.
>>> chars = list(leyenda)
>>> chars
['P', 'o', 't', 'e', 'n', 'c', 'i', 'a', ' ', '(', 'k', 'W', ')']
Para hacer lo segundo y convertir una lista de caracteres a una cadena, se
utiliza join(). Considérense los siguientes dos ejemplos.
>>> ''.join(['P', 'o', 't', 'e', 'n', 'c', 'i', 'a'])
'Potencia'
>>> '-'.join(['P', 'o', 't', 'e', 'n', 'c', 'i', 'a'])
'P-o-t-e-n-c-i-a'
Lo que precede al punto es el carácter que va a ser insertado en medio de los
elementos de la lista. En el primer ejemplo, este carácter es '', la cadena
15
vacía, de modo que se devuelve la cadena original. En el segundo ejemplo,
este carácter es la cadena '-', de modo que se inserta un guion entre los
caracteres. El cuadro 3 reúne todavía más métodos que se pueden aplicar a
cadenas.
Existe una diferencia muy importante entre las listas y las cadenas: las
listas son mutables, lo cual quiere decir que se les puede remover de forma
directa un elemento o se les puede cambiar. Las cadenas, por otro lado, son
inmutables.
16
Cuadro 3. Métodos aplicables a las cadenas. Al igual que los métodos para
listas, se debe escribir un punto (.) después de la cadena y luego el nombre
del método. Véase, como ejemplo, el uso de join(), explicado en el texto.
Método Descripción
center(width) Devuelve una subcadena centrada en el centro de
la cadena original y con un ancho de width
endswith(suffix) Devuelve True si la cadena termina con la subca-
dena suffix
startswith(prefix) Devuelve True si la cadena empieza con la subca-
dena prefix
index(substring) Devuelve el menor índice que contenga a
substring
lstrip([chars]) Devuelve una copia de la cadena después de ha-
ber removido los caracteres contenidos en la lista
[chars] siempre y cuando se encuentren a la iz-
quierda
rstrip([chars]) Igual que lstrip pero ahora removiendo los ca-
racteres si se encuentran a la derecha
strip([chars]) Equivale a aplicar primero lstrip y luego, al re-
sultado, aplicar rstrip
upper() Devuelve una copia de la cadena pero con todos
los caracteres convertidos a mayúsculas
lower() Igual que upper() pero con todos los caracteres
convertidos a minúsculas
title() Devuelve una copia de la cadena pero con todas
las palabras iniciando en mayúscula y los demás
caracteres en minúsculas
replace(old, new) Devuelve una copia de la cadena pero reemplazan-
do la subcadena old por new
split(sep) Separa la cadena original en subcadenas, usando la
cadena sep como delimitador; devuelve una lista
con dichas subcadenas
join([List]) Une las cadenas en List y usa a la cadena que
llama el método como el delimitador
isalpha() Devuelve True si todos los caracteres en la cadena
son alfabéticos y si la cadena no está vacía
isdigit() Igual que isalpha() pero comprueba si todos los
caracteres son dígitos
17
Nótese que si uno de los valores no es una cadena, es convertido por Python
antes de imprimir. Una sintaxis alternativa, conocida como la sintaxis de
f-strings, es la siguiente:
>>> print(f'La corriente es {10} ampere')
La corriente es 10 ampere
2.3 Tuplas
Otra estructura de datos secuencial es la tupla. Estas se definen igual que
las listas pero con paréntesis redondos en lugar de corchetes.
>>> pos = (1, 2, 3)
La mayoría de operaciones que son posibles con las listas son también posibles,
siguiendo la misma sintaxis, con las tuplas.
>>> pos[::-1]
(3, 2, 1)
>>> pos += (4, 5)
>>> pos
(1, 2, 3, 4, 5)
>>> 2*pos
(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
El lenguaje solo impone una diferencia entre listas y tuplas: que estas últimas
son inmutables.
>>> pos[0] = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> del pos[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion
2.4 Conjuntos
No todas las estructuras de datos son secuenciales. Esto está bien, pues
ciertos problemas no demandan en su solución la noción de ordenamiento.
Una estructura de datos no secuencial son los conjuntos. Al igual que en
las matemáticas, un conjunto en Python es una colección no ordenada de
elementos únicos. Para definirlos, se utilizan las llaves ({}).
18
>>> S = {1, 2, 3, 4, 5}
>>> S
{1, 2, 3, 4, 5}
>>> A = {1, 2, 3}
>>> B = {3, 4}
>>> A | B
{1, 2, 3, 4}
>>> A - B
{1, 2}
>>> A & B
{3}
>>> A ^ B
{1, 2, 4}
>>> 1 in {1, 2, 3}
True
>>> A = {1, 2, 3}
>>> 4 in A
False
>>> not 4 in A
True
>>> S = set(range(10))
>>> str(S)
19
'{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}'
>>> tuple(S)
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> list(S)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> S = {3}
>>> S |= {2}
>>> S
{2, 3}
>>> list(S)
[2, 3]
Pese a que muchas veces no tiene sentido pasar de una estructura no secuencial
a una que sí lo es, esto es útil cuando el orden en la estructura secuencial
todavía, en el punto actual del programa, no es relevante. Supóngase que se
tiene la siguiente lista con duplicados.
>>> x = list(set(x_duplicates))
>>> x
[1, 2, 3]
2.5 Diccionarios
Otra estructura de datos no secuencial son los diccionarios. Para motivar
el uso de esta estructura, supóngase que se desea almacenar la información
del cuadro 4. Un primer enfoque podría ser definir una lista para la primera
columna y otra para la segunda.
20
Cuadro 4. Participantes de un curso y su país de residencia. Este cuadro
define una función desde el conjunto de participantes, que es el dominio,
hasta el conjunto de países, que es el codominio.
Participante País Índice
Marta Costa Rica 0
Pedro Guatemala 1
Juan El Salvador 2
0 7→ 'Marta'
1 7→ 'Pedro'
2 7→ 'Juan'
21
Con el lenguaje de funciones, esto equivale a componer las funciones definidas
por cada lista. Si f es la función que asocia participante a cada índice y g
es la que asocia un lugar de residencia a cada índice, entonces la sentencia
anterior corresponde a calcular g(f −1 ('Pedro')).
El objetivo de los diccionarios es implementar directamente esta com-
posición y prescindir, así, de los índices como variables intermedias. La
información del cuadro 4 sería guardada en un diccionario de la siguiente
forma.
>>> paises['Pedro']
'Guatemala'
>>> {}
{}
Esta es, por cierto, la razón por la que los conjuntos vacíos se definen con
set(); ya las llaves están reservadas para los diccionarios. Si se quiere cambiar
una llave, entonces se hace una asignación similar a como se hace con las
listas.
22
>>> paises.pop('Marta')
'Nicaragua'
>>> paises
{'Pedro': 'Guatemala', 'Juan': 'El Salvador'}
>>> paises
{'Pedro': 'Guatemala', 'Juan': 'El Salvador'}
>>> paises['Pablo'] = 'Argentina'
>>> paises
{'Pedro': 'Guatemala', 'Juan': 'El Salvador', 'Pablo': 'Argentina'}
Las llaves de los diccionarios pueden ser cualquier objeto inmutable, como
lo son los datos numéricos, las cadenas o las tuplas. Los que son mutables,
en cambio, no son permitidos.
>>> diccionario = {}
>>> diccionario[0] = 'Llave 1'
>>> diccionario[[1, 2]] = 'Llave 2'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> diccionario[(1, 2)] = 'Llave 3'
>>> diccionario
{0: 'Llave 1', (1, 2): 'Llave 3'}
Los valores, por su parte, pueden ser virtualmente cualquier dato o estructura
de datos que se pueda asignar a una variable.
23
escribir en el intérprete. En los programas completos, es deseable plasmar esas
decisiones en el código mismo. Para ello, se pueden utilizar los condicionales
(sentencias if, elif, else) y los bucles (sentencias for y while).
3.1 Condicionales
En Python, los condicionales se forman con las palabras reservadas if y
else. La sintaxis tiene la siguiente estructura.
if <C1>:
<código ejecutado si la condición C1 evalúa a True>
else:
<código si C1 evalúa a False>
if len(x) != 0:
average = sum(x)/len(x)
else:
average = 0
print('La lista está vacía.')
print(f'Promedio: {average}')
if len(x) != 0:
average = sum(x)/len(x)
else:
average = 0
24
print('La lista está vacía.')
print(f'Promedio: {average}')
La solución de un problema puede requerir condicionales más complejos.
Por ejemplo, ¿qué tal si se desea tomar el promedio con no más que los
primeros diez datos numéricos y desplegar una advertencia si se están descar-
tando datos subsecuentes? Para ello, se pueden tanto componer expresiones
booleanas como también utilizar la palabra reservada elif.
if len(x) != 0 and len(x) <= 10:
average = sum(x)/len(x)
elif len(x) > 10:
average = x[:10]/10
print('Se descartan los datos que siguen al décimo.'
else:
average = 0
print('La lista está vacía.')
print(f'Promedio: {average}')
Python primero evalúa la expresión len(x) != 0 and len(x) <= 10, que
es verdadera, dada la conjunción and, solo si x tiene más que cero y menos
que once elementos. Si esta condición es verdadera, procede como antes;
si no, entonces evalúa la expresión booleana del elif. Si esta expresión es
verdadera, la evalúa, pero si es falsa, salta directamente al else y ejecuta
ese código. También sería posible colocar dos o más sentencias elif después
de un if; no existe límite.
Cabe observar que las instrucciones anteriores son equivalentes, desde la
perspectiva lógica, al siguiente anidamiento.
if len(x) != 0 and len(x) <= =10:
average = sum(x)/len(x)
else:
if len(x) > 10:
average = x[:10]/10
print('Se descartan los datos que siguen al décimo.')
else:
average = 0
print('La lista está vacía.')
print(f'Promedio: {average}')
Sin embargo, esta alternativa tiene dos desventajas. Por un lado, ubica
decisiones similares en niveles de anidamiento distintos, lo cual introduce
25
una diferencia semántica que no existe ahí en el problema original. Por otro
lado, no es escalable: si se tuviesen que tomar cinco decisiones, una después
de la otra, habría que escribir cinco niveles de anidamiento.
Por último, cabe mencionar una abreviatura para los condicionales con
una sola condición (if ... else en lugar de if ... else if ... else).
>>> n = 10
>>> naturals = list(range(1, n + 1))
>>> squares = []
>>> for k in naturals:
... k_squared = k**2
... squares.append(k_squared)
...
26
>>> squares
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Existen técnicas muy frecuentes para aplicar con los bucles for. Por
ejemplo, para sumar dos listas entrada por entrada, se puede iterar no sobre
los elementos de ellas, sino sobre los índices. Para esto, se combinan range()
y len(). Después, dentro del bloque del for, se indexan las listas.
>>> x = [1, 2, 3]
>>> y = [2, 3, 4]
>>> x_plus_y = []
>>> for index in range(len(x)):
... x_plus_y.append(x[index] + y[index])
...
>>> x_plus_y
[3, 5, 7]
Como una alternativa, se puede iterar de modo que la variable no sea una
sola, sino en cambio una tupla; para ello, se utiliza, además, la función zip().
Este código efectúa lo mismo que el anterior.
>>> x = [1, 2, 3]
>>> y = [2, 3, 4]
>>> x_plus_y = []
>>> for (xi, yi) in zip(x, y):
... x_plus_y.append(xi + yi)
...
>>> x_plus_y
[3, 5, 7]
En vez de darle siempre nombres genéricos a la variable de iteración, como i
o j, se puede dar el mismo nombre a la estructura de datos y a la variable
de iteración, pero uno en plural (chars) y otro en singular (char).
>>> chars = list('Python')
>>> for char in chars:
... print(char)
...
P
y
t
h
o
n
27
3.3 Bucles while
A veces es necesario iterar pero no se sabe cuántas veces ni tampoco
cuáles valores debe tomar la variable de iteración. En esos casos, se utilizan
los bucles while. La sintaxis general es la siguiente.
while <C1>:
<Código ejecutado mientras que C1 evalúa a True>
Como antes, los dos puntos son sumamente importantes, así como las sangrías
a la izquierda. Python entra al bloque solo si la expresión booleana es
verdadera y ejecuta el bloque de código. Después de cada iteración, se evalúa
la expresión booleana y se vuelve a iterar solo si es verdadera. Esta expresión
es también conocida como la condición de terminación.
El siguiente es un ejemplo de un bucle while.
>>> i = 0
>>> while i < 3:
... i
... i += 1
...
0
1
2
28
El siguiente código, en cambio, define una lista con los cuadrados de los
números en range(10).
>>> [i**2 for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
El siguiente código define una lista con todos los caracteres de la cadena
'Python' excepto 'y' y 'h'.
>>> x = 'Python'
>>> [i for i in x if i != 'y' and i != 'h']
['P', 't', 'o', 'n']
Como se puede observar, esta es una definición en la que ya no se especifican
explícitamente los elementos de una estructura de datos, sino en cambio lo
que estos elementos deben satisfacer. Esta notación recuerda a la forma en
que se definen conjuntos en las matemáticas: en vez de decir
S = {0, 2, 4, 6, . . .}
se puede decir
S = {2n : n ∈ N} .
Las definiciones por comprensión admiten técnicas que eran posibles con
los bucles for convencionales. Es posible, por ejemplo, que la variable de
iteración sea una tupla en lugar de un dato individual.
>>> [x + y for (x, y) in zip(range(3), range(3))]
[0, 2, 4]
Además, es posible anidar iteraciones. En un código como el que sigue, la
variable y es la que cambia «más rápido».
>>> [(x, y) for x in range(2) for y in range(2)]
[(0, 0), (0, 1), (1, 0), (1, 1)]
Finalmente, es posible aplicar la comprensión también a otras estructuras.
A continuación se define un diccionario a partir de dos listas: una de ellas es
utilizada para las llaves y la otra para los valores.
>>> participantes = ['María', 'Pedro', 'Juan']
>>> paises = ['Panamá', 'Guatemala', 'El Salvador']
>>> dic = {participante: pais
... for participante, pais in zip(participantes, paises)}
>>> dic
{'María': 'Panamá', 'Pedro': 'Guatemala', 'Juan': 'El Salvador'}
29
4 Funciones y archivos
Este apartado expone una de las técnicas de programación modular más
populares: la programación con funciones. Estos objetos relacionan, como en
las matemáticas, un valor de entrada con uno de salida. Su utilidad radica en
que una única implementación puede ser reusada innumerables veces. Así, se
alivia la tarea de programación y se reduce el riesgo de errores. El apartado
también incluye la escritura y lectura de archivos.
30
la variable x adquirirá el valor de 2. Después de los argumentos se encuentran
los dos puntos (:), que son los que marcan la definición de dicha función. Al
igual que en los condicionales, el texto que sigue a los dos puntos debe tener
un nivel de sangría hacia la derecha. Una vez que dicha sangría se revierte
un nivel a la izquierda, Python lo interpreta como el final de la definición.
En este caso, la definición consta de una sola sentencia: return x**2. Esto
es lo que f devuelve al pasarle otros argumentos:
>>> def f(x):
... return x**2
...
>>> f(2)
4
>>> f(3)
9
Las funciones no necesariamente deben recibir argumentos. La siguiente
es una función constante.
>>> def g():
... return 1
...
>>> g()
1
Tampoco es estrictamente necesario que una función devuelva un valor.
Cuando una función no devuelve nada, es equivalente a que tuviera la
sentencia return None al final de su definición.
La función f presentada antes (que devolvía el cuadrado de un número)
luce como una fórmula. Sin embargo, el contenido de una función puede ser
cualquier secuencia de instrucciones como las que se han escrito hasta ahora.
Es importante que dichas instrucciones hagan referencia a variables que sean
«conocidas» para la función (como sus argumentos) y que las instrucciones se
encuentren con el nivel de sangría correcto. La función que sigue devuelve el
valor absoluto de su argumento.
>>> def myabs(x):
... if x > 0:
... return x
... else:
... return -x
...
31
>>> myabs(3)
3
>>> myabs(-3)
3
Las funciones pueden combinarse con las estructuras de datos y de control
de flujo estudiadas hasta ahora, lo cual es sumamente poderoso. Supóngase
que se tienen mediciones de tensiones en pu y se quiere determinar cuántas de
ellas están fuera del intervalo 0,95 pu–1,05 pu. Las mediciones están guardadas
en una lista voltages.
>>> voltages = [0.93, 0.94, 0.99, 1.00, 1.03, 1.06, 1.07]
Para empezar, se puede definir una función que determine si una sola tensión
está fuera de ese rango.
>>> def is_outside(v):
... return not (0.95 <= v <= 1.05)
...
Después, usando la comprensión de listas unida con los condicionales, se
filtran las tensiones que devuelven True.
>>> wrong_voltages = [v for v in voltages if is_outside(v)]
En el condicional se pudo haber escrito if is_outside(v) == True, pero
esto es innecesario: ya la función is_outside devuelve, de por sí, un valor
booleano. Finalmente, se calcula la longitud de la lista filtrada:
>>> len(wrong_voltages)
4
Con la práctica, es natural prescindir de las variables intermedias (como
wrong_voltages) y que simplemente se escriba todo en una sola sentencia:
>>> outliers = len([v for v in voltages if is_outside(v)])
>>> outliers
4
32
Cuadro 5. Modos de apertura de un archivo.
Modo Significado
'r' abrir para leer (por defecto)
'w' abrir para (sobre)escribir, creando el archivo si no existe
'x' abrir para crear (falla si el archivo ya existe)
'a' abrir para escribir, anexando al final del archivo si ya existe
'b' modo binario
't' modo de texto (por defecto)
'+' abrir un archivo del disco para actualizarlo (leer y escribir)
33
Para evitar que todo el contenido quede en una sola línea, suele utilizarse el
carácter '\n'. Así,
with open('Ejemplo 1.txt', 'w') as f:
f.write('Primera línea\n')
f.write('Segunda línea')
produciría
Primera línea
Segunda línea
Otro modo muy utilizado es el de lectura. Las siguientes instrucciones
leerían el archivo recién creado:
>>> with open('Ejemplo 1.txt', 'r') as f:
... for line in f:
... print(line)
...
Primera línea
Segunda línea
En esencia, la lectura de archivos siempre se reduce a iterar sobre f. Esto
equivale a iterar sobre los renglones del archivo. Para procesar el contenido
del archivo, se pueden usar todos los métodos estudiados antes. Las siguientes
instrucciones, por ejemplo, se deshacen de los saltos de línea y guardan todas
las palabras en una lista.
>>> all_words = []
>>> with open('Ejemplo 1.txt', 'r') as f:
... for line in f:
... words = line.strip('\n').split(' ')
... all_words += words
...
>>> all_words
['Primera', 'línea', 'Segunda', 'línea']
5 Ejercicios
Ejercicio 1. Es posible aproximar el número π mediante la llamada fórmula
de Leibniz, según la cual
1 1 1 1 π
1− + − + + ··· = .
3 5 7 9 4
34
Escriba un programa que tome estos primeros cinco sumandos e imprima
el error absoluto de la aproximación. Como ejemplo, si solo se tomasen los
primeros tres sumandos, el resultado sería π/4 ≈ 0,8666666666666667 o, lo
que es equivalente, π ≈ 3,466666666666667. El error absoluto sería entonces
de 0,32507401307687367 y se debería imprimir, para este ejemplo,
Ejercicio 4. Explique las diferencias entre las listas, las tuplas, los conjuntos
y los diccionarios.
35
1,04
Tensión [pu]
1,02
(La indentación del for es meramente estilística.) Después, cargue los conte-
nidos de los archivos voltages.txt, potencias.txt y meters.txt. Aquí se
supone que se encuentran en un directorio hijo llamado datos, pero podría ser
necesario cambiarlos según la ubicación de los archivos en su computadora.
v = file2list('data/voltages.txt')
meters = file2list('data/meters.txt', type=str)
Para facilitar el análisis, los datos de voltages.txt son graficados en la
figura 4.
Ejercicio 6. Encuentre los valores mínimo, máximo y promedio de las
tensiones registradas.
Ejercicio 7. De forma automatizada, despliegue una advertencia si las
tensiones superan en algún momento 0,5 pu; el mensaje puede ser
'Las tensiones superaron 1.05 pu.'
Similarmente, despliegue una advertencia si alguna tensión está por debajo
de 0,95 pu.
36
Residencia
Potencia activa [kW]
1,5
0 5 10 15 20
Tiempo [h]
Ejercicio 9. Cree una lista que tenga las mediciones por hora y no por
minuto.
import math
37
approx_pi = 4*(1 - 1/3 + 1/5 - 1/7 + 1/9)
abs_error = abs(approx_pi - math.pi)
print(f'El error absoluto con cinco sumandos es {abs_error}.')
El resultado es
4 Las listas, las tuplas, los conjuntos y los diccionarios se diferencian en los
siguientes aspectos:
38
Acceso a los datos En las listas y las tuplas se utiliza un índice, que es un
número entero. En los diccionarios se utilizan las llaves, que puede ser
cualquier dato inmutable; ejemplos son los datos de tipo int, float,
str y bool. En los conjuntos, por el contrario, no es posible obtener
un dato específico, pese a que sí se puede iterar sobre todos los datos o
acceder a uno aleatorio.
Presencia de duplicados Tanto las listas como las tuplas pueden contener
duplicados. Los diccionarios pueden tenerlos en los valores pero no en
las llaves. Los conjuntos no contienen duplicados del todo.
5 Se analizan los cuatro casos:
1. Como la variable temporal es importante, conviene utilizar una estruc-
tura secuencial. Entre las listas y las tuplas, se prefieren las listas por
la facilidad para modificarlas (por ejemplo, anexar datos nuevos al final
de la lista).
2. Como la variable ya no es el tiempo sino el espacio, en el cual no suele
existir un ordenamiento intrínseco, se pueden usar estructuras tanto
secuenciales como no secuenciales. Sin embargo, como posiblemente se
quiera asociar cada medición a otro dato y, así, se quiera tener pares
de datos, conviene almacenarlos en un diccionario: la ubicación sería la
llave y la medición el valor.
3. Como el ordenamiento no es necesario, se puede usar una estructura no
secuencial. Una opción sería tener el modelo del equipo como la llave
de un diccionario y luego otra información relevante, como cantidad
de ejemplares y año de compra, como el valor; como el valor consta
de varios datos, se podría almacenar, a su vez, en otra estructura de
datos, como una tupla.
4. Por la misma razón que el punto 2, se puede utilizar un diccionario,
cuyas llaves sea el nombre de la subestación (aprovechando que es
único) y cuyos valores sean las tensiones nominales.
6 Una forma de obtener el mínimo sería implementar el algoritmo manual-
mente:
min_v = 2
for vi in v:
if vi < min_v:
min_v = vi
print(f'El menor valor es {min_v}.')
39
Al correrlo, se imprime lo siguiente:
Esto resulta en
with open('data/potencias.txt') as f:
potencias = [float(line) for line in f]
p_promedio = round(sum(potencias)/len(potencias), 2)
print(f'El promedio anual es {p_promedio} kW.')
Lo anterior resulta en
40
Se ha utilizado la función round para imprimir la potencia solamente con
dos decimales. Segundo, la potencia consumida durante enero se calcula de
la misma forma pero tomando primero una rebanada:
horas_enero = 24 * 31
p_enero = potencias[:horas_enero]
p_enero_promedio = round(sum(p_enero)/len(p_enero), 2)
print(f'El promedio de enero es {p_enero_promedio} kW.')
Lo anterior resulta en
i = 0
para un dt igual al que separa los datos contenidos en p_enero. Como dicha
separación es de una hora (dt = 1), basta con calcular una sumatoria:
energia_enero = round(sum(p_enero), 1)
print(f'La energía consumida en enero es de {energia_enero} kWh.')
p_min = round(min(potencias), 2)
p_max = round(max(potencias), 2)
print(f'Potencias mínima y máxima: {p_min} kW y {p_max} kW.')
Lo anterior resulta en
v_horario = v[::60]
41
En este caso se tomó una rebanada con un paso de 60. El resultado de
print(len(v_horario)) es 24, mientras que el de print(v_horario[:3]),
para corroborar solo las primeras tres mediciones, es
N = len(set(meters))
print(f'La cantidad de modelos únicos es {N}.')
El resultado es
42