0% encontró este documento útil (0 votos)
32 vistas42 páginas

Apuntes

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
32 vistas42 páginas

Apuntes

Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 42

Fundamentos de Python

Ing. Francisco Escobar Prado

Actualizado el 13 de marzo de 2023

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

3 Estructuras de control de flujo 23


3.1 Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Bucles for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3 Bucles while . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.4 Definición por comprensión . . . . . . . . . . . . . . . . . . . 28

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

6 Soluciones de los ejercicios 37

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

fn = fn−1 + fn−2 (1)

para n ≥ 2. La sucesión empieza entonces con 0, 1, 1, 2, 3, 5, 8, 13, . . . En


Python, la función que devuelve el n-ésimo término puede ser implementada
así:

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

Una ventaja de este modo es que no requiere ninguna etapa de compilación,


sino que las sentencias son interpretadas de forma inmediata. Es posible, por
lo tanto, utilizar el lenguaje como si fuese una calculadora. Una desventaja
es que las sentencias interpretadas no pueden ser modificadas, por lo que se
limita a operaciones sencillas.
Para salir del modo interactivo, se utiliza el carácter de final de línea,
que es Control-Z en Windows y Control-D en Linux y MacOS.

1.3 Tipos de datos


Al escribir programas, es necesario almacenar datos de distinta naturaleza.
Cuando se debe operar sobre los datos porque hay que contar elementos,
dividir precios en una base de datos o implementar métodos numéricos, se
debe disponer de datos numéricos. Cuando, en cambio, se debe almacenar
información que luego debe ser legible para humanos, entonces se debe
disponer de cadenas de caracteres. Además, si es necesario tomar decisiones
constantemente y disponer de variables que puedan tomar solo uno de dos
valores (verdadero o falso), entonces se debe disponer de datos lógicos. A
esta distinción entre los datos se le llama su tipo.
Los datos numéricos son ingresados directamente desde el teclado con
las teclas numerales y pueden ser números enteros, números en formato de
coma flotante (representación de ciertos números reales) o números complejos.
Para ingresar un número entero o un número en formato de coma flotante,
solamente se le digita.
1
Las líneas que empiezan con tres flechas (>>>) indican lo que el usuario escribiría,
mientras que las líneas que carecen de ellas indican lo que Python responde. La respuesta
es desplegada tras presionar la tecla de retorno: .

3
>>> 42
42
>>> 3.141592
3.141592

En Python 3, un número entero, como 42, no se diferencia de su contraparte


con coma flotante, 42.0. También es posible ingresar los números con notación
científica. El siguiente número corresponde a 9,9 × 10−31 .

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

>>> - 2.71828/2 + (1 + 1/2 + 1/6 + 1/24 + 1/120)/2


-0.5008066666666666

Cuando no se quiere realizar la división exacta sino que se quiere recuperar


solo la parte entera de la división o solo el residuo, se usan los operadores //
y %.

>>> 45 // 7
6
>>> 45 % 7
3

Finalmente, la exponenciación se lleva a cabo con el operador **.

>>> 2**3
8

Las cadenas de caracteres pueden ser especificadas con apóstrofes o con


comillas dobles sin ninguna diferencia en cuanto al dato que es almacenado.

4
>>> 'Potencia consumida (kW)'
'Potencia consumida (kW)'
>>> "Potencia consumida (kW)"
'Potencia consumida (kW)'

Una operación admitida por las cadenas es la concatenación, que permite


unir dos cadenas y que es llevada a cabo con el operador +.

>>> 'De este modo' + ' ' + 'se concatenan cadenas.'


'De este modo se concatenan cadenas.'

Otra operación es la indexación, que permite acceder a caracteres particulares


de una cadena. Es posible acceder, por ejemplo, al quinto elemento de la
cadena de la instrucción anterior.

>>> _[4]
's'

La indexación en Python empieza a contarse en 0, por lo cual el quinto


elemento es, como se ve, el de índice 4.
Por último, los datos lógicos pueden tomar solo uno de dos valores,
equivalentes a «verdadero» y «falso».

>>> True
True
>>> False
False

Es posible obtener datos lógicos a partir de comparaciones de los otros tipos


de datos, como por ejemplo al verificar igualdad.

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

>>> 'Hola' in 'Hola, mundo'


True

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

1. se distingue entre mayúsculas y minúsculas;


2. se puede inclur cualquier letra y dígito;
3. no se puede iniciar con un dígito;
4. no se pueden utilizar las palabras reservadas (ver cuadro 1).

1.5 Edición de archivos y programas completos


Si bien el modo interactivo es sumamente práctico para hacer cálculos
rápidos y experimentar con el lenguaje, no es práctico componer en él
programas grandes. Para ello, Python ofrece la alternativa del modo de script
o de programas completos. Este modo consiste en escribir instrucciones como
las del modo interactivo pero ahora en un archivo de texto, una tras otra.
Es importante señalar que no hay nada especial en la extensión de archivos
.py ni tampoco en el editor Spyder. Python presta atención al contenido del
archivo, no a su nombre. Además, es posible ejecutar un archivo de forma tan
primitiva como desde una terminal. La extensión .py es una convención que
le permite a programas de terceros, como Spyder u otros editores, identificar
un archivo de Python y así saber con cuál programa correrlo y cuáles palabras
resaltar gráficamente. Spyder es utilizado aquí por ser uno de los editores
más populares.

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

# Inicializar lista con sucesión y valores iniciales.


sequence = []
a, b = 0, 1
# Iterar
while a < n:
# Guardar término actual
sequence.append(a)
# Aplicar relación de recurrencia
a, b = b, a + b # La asignación debe ser simultánea

return sequence

Cuando se quiere escribir comentarios de más de una línea, se utilizan ya sea


las tres apóstrofes, que permiten saltos de línea, o se repite el símbolo # al
inicio de cada línea.

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

Listas Tuplas Diccionarios Conjuntos


[1, 'a'] (1, 'a') {'tol': 1e-3} {1, 2}

Figura 1. Estructuras de datos predefinidas en Python y su secuencialidad.

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.

>>> x = ['A', 'B', 'C', 'D']


>>> x
['A', 'B', 'C', 'D']

Las listas pueden contener cualquier tipo de dato, incluso mezclados. La


siguiente sentencia, que conviene aprender de memoria, define una lista con
diez números naturales que empiezan en 0.

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

Para calcular la suma de los elementos, se utiliza la función sum().

>>> sum(y)
45

Con estas dos funciones es posible calcular el promedio de cualquier lista de


datos numéricos.

>>> average = sum(y)/len(y)


>>> average
4.5

También se pueden calcular los elementos mínimo y máximo.

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

>>> x = ['A', 'B', 'C', 'D']


>>> x[1]
'B'

En ocasiones se vuelve necesario acceder no solamente a un elemento de


una lista, sino a varios al mismo tiempo. Considérese la siguiente lista de
caracteres.

>>> x = ['P', 'y', 't', 'h', 'o', 'n']

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']

El resultado incluye el elemento asociado al primer índice especificado, pero


no el asociado al segundo. Una forma útil de pensar las rebanadas en Python
es interpretar los índices como apuntando a los espacios entre los elementos
de la lista y a los delimitadores, en lugar de pensarlos como apuntando a los
elementos. El índice 0 apunta al delimitador de apertura ([), el índice 3, por
ejemplo, apunta a la tercera coma (,) y, por lo tanto, la instrucción x[0:3]
devolvería todos los elementos ubicados entre el delimitador de apertura y la
tercera coma.

>>> x
['P', 'y', 't', 'h', 'o', 'n']
>>> x[0:3]
['P', 'y', 't']

Esta lógica es ilustrada en la figura 2 (por ahora, préstese atención solo a


los índices positivos). Pese a que una lista x tiene índices que llegan hasta
len(x) - 1 (dado que los índices empiezan en cero), es posible pasar el índice
len(x) cuando se toma una rebanada. Este índice apunta al delimitador de
cierre. Así, la siguiente rebanada es equivalente a la lista completa.

>>> 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[-4:-2] = [’t’, ’h’]

Figura 2. Significado de los índices en la toma de rebanadas. La lista x había


sido definida como x = ['P', 'y', 't', 'h', 'o', 'n'].

Las rebanadas no deben corresponder necesariamente a elementos consecuti-


vos, sino que pueden estar espaciados por un paso (step) definido. Este paso
es opcional y es especificado como si fuese un tercer índice.

>>> x[0:len(x):2]
['P', 't', 'o']
>>> x[::2]
['P', 't', 'o']

Si se indexa la lista con valores negativos, entonces se tiene acceso a los


elementos pero contados en orden inverso.

>>> x = ['P', 'y', 't', 'h', 'o', 'n']


>>> x[-1]
'n'
>>> x[-2]
'o'
>>> x[-len(x)]
'P'
>>> x[::-1]
['n', 'o', 'h', 't', 'y', 'P']
>>> x[::-2]
['n', 'h', 'y']

El significado de los índices negativos es también ilustrado en la figura 2.


Además de acceder o cambiar los datos de una sola lista, es posible operar
sobre diferentes listas. El operador + permite concatenar dos listas.

>>> 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]

Para insertar un elemento, se utiliza insert(), mientras que para removerlo


se utiliza remove().

>>> x = ['A', 'B', 'D']


>>> x.insert(2, 'C')
>>> x
['A', 'B', 'C', 'D']
>>> x.remove('C')
>>> x
['A', 'B', 'D']

Otras operaciones sumamente útiles son las de comprobar pertenencia a una


lista y buscar el índice asociado a un elemento dado. Para esto se utilizan in
e index().

>>> x = ['B', 'A', 'C', 'D', 'C']


>>> 'C' in x
True
>>> x.index('C')
2

Siempre se devuelve el índice solo de la primera aparición del elemento. El


cuadro 2 reúne todavía más métodos.

2.2 Más sobre cadenas


Además de las listas, otro ejemplo de estructuras secuenciales son las ya
estudiadas cadenas. Con ellas, es posible tanto la indexación como la toma
de rebanadas.

>>> leyenda = 'Potencia (kW)'


>>> leyenda[0]
'P'
>>> leyenda[0:5]
'Poten'

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.

>>> leyenda = 'potencia consumida (kW)'


>>> leyenda[0] = 'P'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

No obstante, estas operaciones pueden ser ejecutadas usando rebanadas o,


de ser necesario, convirtiendo la cadena a lista, ejecutando la operación y
luego devolviendo el resultado a una cadena.

>>> leyenda[:10] + leyenda[11:]


'potencia cnsumida (kW)'
>>> x = list(leyenda)
>>> del x[10]
>>> ''.join(x)
'potencia cnsumida (kW)'

Finalmente, una operación frecuente con las cadenas es imprimirlas en la


línea de comandos; si se trabaja con Spyder, esta es la ventana en la que se
utiliza el modo interactivo. Para esto se utiliza print. Dicha función recibe
las cadenas que debe imprimir y, opcionalmente, los caracteres que utilizará
como separadores y como final de línea. Por ejemplo,

>>> print('La corriente es', 10, 'ampere', sep=' ', end='.\n')


La corriente es 10 ampere.
>>> print('La corriente es', 10, 'ampere', sep='...', end='!\n')
La corriente es...10...ampere!

Si no se pasa explícitamente sep, las cadenas se separan por espacios; si no


se pasa end, se terminan con '\n', que equivale a un salto de línea.

>>> print('La corriente es', 10, 'ampere')


La corriente es 10 ampere

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}

Los conjuntos no necesariamente deben tener datos numéricos. El siguiente


ejemplo muestra uno con cadenas. El ejemplo ilustra, además, que la variable S
termina conteniendo solo una vez a la cadena 'Pedro'.

>>> S = {'Pedro', 'Marcos', 'Pedro'}


>>> S
{'Pedro', 'Marcos'}

Los conjuntos admiten las operaciones que se conocen de la teoría de


conjuntos, como la unión (operador |), la diferencia (-), la intersección (&) y
la diferencia simétrica (^).

>>> 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}

Adicionalmente, se puede verificar la pertenencia de un elemento a un con-


junto usando la palabra reservada in.

>>> 1 in {1, 2, 3}
True
>>> A = {1, 2, 3}
>>> 4 in A
False
>>> not 4 in A
True

Los conjuntos pueden ser convertidos a cualquiera de las estructuras


presentadas arriba.

>>> 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]

Debe tomarse en cuenta, sin embargo, que al convertir un conjunto a una


estructura secuencial el orden resultante no necesariamente va a tener sentido.
Considérese el siguiente ejemplo, en el que el orden en que los elementos
fueron agregados al conjunto difieren del orden en que aparecen tras la
conversión a una estructura secuencial.

>>> 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_duplicates = [1, 2, 3, 3]

Para eliminarlos, es posible convertir la lista a un conjunto con set() y luego


devolver el resultado a una lista.

>>> x = list(set(x_duplicates))
>>> x
[1, 2, 3]

Si bien esto no garantiza el orden correcto de los elementos en x, puede ser


que esta lista tuviese que ser ordenada con sort() de todos modos en un
punto posterior del programa.

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

>>> participantes = ['Marta', 'Pedro', 'Juan']


>>> paises = ['Costa Rica', 'Guatemala', 'El Salvador']
Si bien estas dos listas son independientes, la información contenida en
cada una está relacionada por medio de una tercera variable: el índice. Este
es mostrado en la tercera columna del cuadro 4. Si en algún momento se
desease determinar la residencia de, por ejemplo, Pedro, entonces bastaría
con determinar su índice en la lista participantes y luego obtener, mediante
indexación, la entrada correspondiente de la lista paises.
En esencia, lo que el procedimiento anterior escribe es la composición de
funciones. Las listas, como cualquier estructura de datos secuencial, pueden
ser interpretadas como una función desde los números naturales (domi-
nio) hasta el conjunto formado por sus elementos (codominio). Así, asignar
participantes = ['Marta', 'Pedro', 'Juan'] es definir de forma implí-
cita una función f dada por

0 7→ 'Marta'
1 7→ 'Pedro'
2 7→ 'Juan'

Suponiendo que la lista no tiene duplicados, cada elemento de la lista se


asocia a uno y solo un índice, de modo que la función f es invertible. Se
reconoce que dada una preimagen (un índice) es posible obtener la imagen
(el elemento) mediante la indexación. Similarmente, dada una imagen (el
elemento) es posible obtener la preimagen (el índice) mediante la sentencia
.index().
En el ejemplo anterior, si se desease encontrar el país de residencia de
Pedro, entonces se escribiría la siguiente sentencia.
>>> paises[participantes.index('Pedro')]
'Guatemala'

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 = {'Marta': 'Costa Rica',


... 'Pedro': 'Guatemala',
... 'Juan': 'El Salvador'}

El diccionario es delimitado por los símbolos {} y \pyv{}, mientras que sus


elementos son separados por comas. Cada elemento consta de dos partes:
una llave, que se ubica a la izquierda de los dos puntos (:), y un valor, que
se ubica a la derecha. Así, la sintaxis general es

diccionario = {llave_1: valor_1, llave_2: valor_2, ...}

Como ya se prescindió de los índices, la forma de encontrar el valor (imagen)


asociado a cada llave (preimagen) es pasando la llave al diccionario.

>>> paises['Pedro']
'Guatemala'

Si se quiere crear un diccionario vacío, entonces se utilizan dos llaves


consecutivas.

>>> {}
{}

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.

>>> paises['Marta'] = 'Nicaragua'


>>> paises
{'Marta': 'Nicaragua', 'Pedro': 'Guatemala', 'Juan': 'El Salvador'}

Si se desea eliminar una entrada, se utiliza .pop() y se le pasa como argu-


mento la llave que debe eliminarse.

22
>>> paises.pop('Marta')
'Nicaragua'
>>> paises
{'Pedro': 'Guatemala', 'Juan': 'El Salvador'}

Esta instrucción devuelve también el valor asociado a la llave eliminada, en


caso de que sea necesario almacenarlo en otra parte. Para agregar una nueva
entrada al diccionario, nótese que no se requiere que la llave nueva exista.

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

>>> diccionario[1] = ('Marta', 24, 'Costa Rica')


>>> diccionario
{0: 'Llave 1', (1, 2): 'Llave 3', 1: ('Marta', 24, 'Costa Rica')}
>>> diccionario[1][2]
'Costa Rica'

3 Estructuras de control de flujo


En los apartados anteriores, cuando se deseó ejecutar una instrucción
u otra en el modo interactivo, era el usuario quien decidía cuál instrucción

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>

Por ejemplo, si se deseara escribir un programa que calcule el promedio de


una lista x cualquiera, entonces se formaría una expresión booleana que sea
verdadera solo si la lista no está vacía.

if len(x) != 0:
average = sum(x)/len(x)
else:
average = 0
print('La lista está vacía.')

print(f'Promedio: {average}')

El comportamiento exacto de Python al encontrarse estas sentencias será el


siguiente: al encontrar la palabra reservada if, evalúa la expresión booleana.
Si el resultado de la evaluación es True, entonces ejecuta el código del bloque
if y no ejecuta el del else; si, encambio, es False, entonces ejecuta el código
del else pero no el del if. Dado que estas sentencias controlan el código que
es ejecutado por Python, son conocidas como una estructura de control.
Es muy importante observar que Python reconoce exactamente dónde
comienza y dónde termina cada bloque según la sangría que se deja a la
izquierda. Así, si se toma el caso particular x = [1, 3], el código anterior
imprimiría Promedio: 2, mientras que el siguiente código imprimiría además
la cadena La lista está vacía.

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

average = sum(x)/len(x) if len(x) != 0 else 0

Esta abreviatura es otro ejemplo más de lo que en Python se conoce como


azúcar sintáctica: un añadido a la sintaxis diseñado para facilitar la tarea de
programar.

3.2 Bucles for


Además de controlar flujo mediante condicionales, Python permite iterar
por medio de los bucles. El que es descrito en este apartado es el bucle for,
utilizado cuando se sabe exactamente sobre cuáles variables se debe iterar.
Considérese el problema de calcular los cuadrados de los primeros n
números naturales. A diferencia de problemas similares, como construir
una lista con los números pares menores que n, este problema no puede
ser resuelto únicamente con la función range y con la toma de rebanadas.
Como se sabe que se deben calcular exactamente n cuadrados, entonces este
problema se presta para aplicar los bucles for. La sintaxis es la siguiente:

for <variable> in <estructura de datos>:


<código ejecutado en cada iteración>

Aquí, <variable> es un nombre válido para una variable, la cual es definida


en esa misma sentencia; es decir, a diferencia de otros lenguajes como C,
no es necesario inicializar antes la variable de iteración indicando su tipo.
Además, tampoco es necesario indicar cómo va a cambiar en cada iteración:
Python simplemente le asignará cada posible valor que se encuentre en
<estructura de datos>. Esta estructura puede ser cualquiera de las estu-
diadas arriba. Las siguientes instrucciones resuelven el problema planteado.

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

Python entra al bucle porque la condición i < 3 es verdadera desde el inicio.


Luego se actualiza el valor de i y el bucle se detiene cuando i alcanza el
valor de 3. Se observa que para que un bucle while termine, es necesario
que ocurran actualizaciones de las variables involucradas en la condición de
terminación.

3.4 Definición por comprensión


Python ofrece una forma de construir estructuras de datos sin necesidad
de usar bucles for, incluso cuando la construcción requiera iterar. Esta es la
llamada definición por comprensión (en inglés comprehension) y es ilustrada
por medio de ejemplos. El siguiente código define una lista que contiene todos
los números en range(20) siempre y cuando sean divisibles por 3. Para esto,
se utiliza la operación módulo.

>>> [i for i in range(20) if i % 3 == 0]


[0, 3, 6, 9, 12, 15, 18]

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.

4.1 Funciones con y sin valores de retorno


Una función es una regla que asocia elementos de dos conjuntos, llamados
dominio y codominio. Específicamente, una función f asocia a cada elemento
del dominio A un elemento del subdominio B. Esto se escribe formalmente
como f : A → B.
El concepto de función predomina en el pensamiento del ser humano.
Es fácil enumerar ejemplos de funciones en la vida cotidiana. Uno es el
hecho de que todas las personas tengan un número de identificación I: esta
es, en esencia, una función del conjunto «personas» (P ) al conjunto de los
números naturales (N); o sea, es una función I : P → N. Otro ejemplo es la
función «madre» M : a cada elemento del conjunto «personas» (P ) se asocia
un elemento «madre» que también pertenece a dicho conjunto; es decir, es
una función M : P → P . Cuando se habla de la velocidad instantánea de
un vehículo se está hablando, en esencia, de una función temporal: a cada
instante de tiempo se asocia un número, que es la velocidad del vehículo.
Las funciones en Python son igual de generales. Tanto su entrada como
su salida pueden tener cualquier valor que pueda ser asignado a una variable.
Esto incluye los valores booleanos True y False, los números como -5 o
3.14, las cadenas de caracteres, las listas, las listas de listas... Incluso es
posible que una función reciba o devuelva otras funciones.
Considérese primero una función de la forma f : R → R, como lo es
f (x) = x2 . Su implementación sería
def f(x):
return x^2
La palabra reservada def marca el inicio de la función y es seguida con el
nombre, que en este caso es f. Lo que está entre paréntesis es una variable
que adquirirá el valor dado a la función, conocido como argumento. Así, una
vez que se escriba
f(2)

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

4.2 Lectura y escritura de archivos


Otras operaciones que involucran la noción de entrada y salida son la
lectura y escritura de archivos. Ambas operaciones son llevadas a cabo con la
misma función, que es open. Además, esta función suele utilizarse en conjunto
con la palabra reservada with, dado lugar a la siguiente sintaxis:

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)

with open('ejemplo.txt', 'w') as f:


f.write('Hola, mundo\n')
<demás código ejecutado mientras el archivo está abierto>
La combinación with open marca la apertura de un archivo, que en este
caso es 'ejemplo.txt'. Este archivo puede existir o no, según el modo en
que se le abra. El modo es el segundo argumento de open y en este caso es
el carácter 'w'; este corresponde al modo de escritura. En dicho modo, si
el archivo no existe, entonces es creado primero. Otro modo, 'r', que es
lectura, arrojaría un error si el archivo todavía no existiese. Los modos en
que se puede abrir un archivo son mostrados en el cuadro 5.
Después de la llamada a open se asocia una variable al archivo recién
abierto: f. Esta variable pudo haber adquirido cualquier nombre, como
file. Con ella, es posible operar sobre el archivo. Dichas operaciones deben
incluirse dentro del cuerpo de la sentencia with: todas las instrucciones que
se ubiquen un nivel de sangría a la derecha. Una vez que se haya salido de
dicho cuerpo, Python cerrará automáticamente el archivo.
La operación que se especificó en el ejemplo, permitida en el modo 'w'
pero no en el 'r', es la de escritura. Esta se lleva a cabo con el método
write. Al escribir f.write(<string>), se escribirá la cadena <string> en
el archivo. Dicha cadena será escrita inmediatamente después del último
carácter escrito en el archivo. Esto es ilustrado con las instrucciones
with open('Ejemplo 1.txt', 'w') as f:
f.write('Primera línea')
f.write('Segunda línea')
que producen un archivo 'Ejemplo 1.txt' que contiene
Primera líneaSegunda línea

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,

El error absoluto con tres sumandos es 0.32507401307687367.

Ejercicio 2. Utilizando el módulo cmath, calcule la magnitud y el ángulo


del número complejo
10 + j5
+ 2 60◦ + 1 .
8−j
Ejercicio 3. Determine teóricamente la salida de la siguiente función boo-
leana y luego compárela con la salida producida por Python.

True and ('Python' == 'PYTHON' or not 1 <= 2 or 1 != 2)

Ejercicio 4. Explique las diferencias entre las listas, las tuplas, los conjuntos
y los diccionarios.

Ejercicio 5. Para los siguientes conjuntos de datos hipotéticos, indique


cuál estructura de datos es más apropiada y por qué. Compare ventajas y
desventajas de cada estructura candidata para el problema específico:

1. mediciones de tensión eficaz recolectadas a lo largo de un día en un


solo lugar,

2. mediciones de tensión eficaz tomadas prácticamente en el mismo ins-


tante pero en diferentes puntos del país,

3. inventario de transformadores de una empresa eléctrica,

4. tensión nominal en kV en las diferentes subestaciones del país, cada


una de las cuales posee un nombre único.

Para los ejercicios que siguen, defina la función file2list como se


muestra:

def file2list(filename, type=float):


with open(filename) as f:
return [float(line) if type == float else line.strip()
for line in f]

35
1,04

Tensión [pu]
1,02

0 200 400 600 800 1,000 1,200 1,400


Tiempo [min]

Figura 3. Tensiones medidas. Los datos de las mediciones están contenidos


en voltages.txt.

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

Ejercicio 8. Cargue los datos del archivo potencias.txt en una estructura


de datos apropiada y determine, sin utilizar ningún módulo,
1. la potencia promedio consumida durante el año,

2. la potencia promedio consumida duramente el mes de enero (31 días),

36
Residencia
Potencia activa [kW]
1,5

0 5 10 15 20
Tiempo [h]

Figura 4. Datos numéricos manipulados en los problemas. La gráfica muestra


parte de las potencias contenidas en el archivo potencias.txt.

3. el consumo de energía del mes de enero y

4. las potencias mínima y máxima alcanzadas durante el año.

Recuerde reportar estas cantidades con las unidades que corresponden.

Ejercicio 9. Cree una lista que tenga las mediciones por hora y no por
minuto.

Ejercicio 10. La lista meters contiene modelos ficticios de medidores inteli-


gentes, posiblemente repetidos. Determine cuántos modelos distintos hay.

Ejercicio 11. Suponga que los modelos de medidor incluidos en la lista


meters son incorrectos: todos empiezan con las letras AM y son seguidos
por números, cuando en realidad deben empezar con AMI. Modifique la lista
meters de modo que los nombres queden corregidos.

6 Soluciones de los ejercicios


1 Como valor exacto, se puede tomar la constante pi de math, por lo cual se
empieza importando dicho módulo.

import math

Después, se aproxima el valor de π, se calcula el error absoluto y se imprime


el resultado.

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

El error absoluto con cinco sumandos es 0.19808988609274714.

2 La utilidad de cmath es que facilita ingresar números complejos en coorde-


nadas polares, mediante su función rect. También facilita la conversión de
coordenadas rectangulares a polares mediante polar.

>>> import cmath


>>> w = cmath.rect(2, 60*cmath.pi/180)
>>> z = (10 + 5j)/(8 - 1j) + w + 1
>>> mag, rad = cmath.polar(z)
>>> mag
4.02531428444631
>>> rad*180/cmath.pi
38.417557029423214

3 En el nivel mayor se encuentra la conjunción and. Dado que el primer


valor es True, el valor lógico de toda la expresión es el valor de lo que se
encuentra entre paréntesis. La primera comparación vale False porque la
igualdad requiere que las mayúsculas y minúsculas coincidan. La segunda
vale también False por la negación not. Por último, la tercera vale True.
Como los tres valores están conectados por or, toda la expresión vale True.
Esto se verifica en el intérprete:

>>> True and ('Python' == 'PYTHON' or not 1 <= 2 or 1 != 2)


True

4 Las listas, las tuplas, los conjuntos y los diccionarios se diferencian en los
siguientes aspectos:

Secuencialidad Las listas y las tuplas preservan un ordenamiento entre los


datos (son estructuras secuenciales), mientras que los diccionarios y los
conjuntos no tienen ese ordenamiento.

Mutabilidad Las listas, los diccionarios y los conjuntos son mutables, lo


cual significa que pueden ser modificados tras su creación. Las tuplas,
por el contrario, son inmutables.

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:

El menor valor es 0.9948185986308011.

Sin embargo, es más práctico aprovechar las funciones ya predefinidas en


Python:

print(f'El valor mínimo es {min(v)}.')


print(f'El valor máximo es {max(v)}.')
print(f'El valor promedio es {sum(v)/len(v)}.')

Esto resulta en

El valor mínimo es 0.9948185986308011.


El valor máximo es 1.0521929880393088.
El valor promedio es 1.03104055135811.

7 Ya disponiendo de los valores máximo y mínimo, simplemente se les utiliza


en condicionales:

if max(v) > 1.05:


print('Las tensiones superaron 1.05 pu.')
if min(v) < 0.95:
print('Las tensiones estuvieron por deajo de 0.95 pu.')

El resultado para este conjunto de datos es, como se esperaría,

Las tensiones superaron 1.05 pu.

8 Para preservar el ordenamiento, se cargan los datos a una lista. Aquí se


supone que los datos están en el directorio data.

with open('data/potencias.txt') as f:
potencias = [float(line) for line in f]

Primero, la potencia promedio consumida durante el año es simplemente el


promedio de todos los datos.

p_promedio = round(sum(potencias)/len(potencias), 2)
print(f'El promedio anual es {p_promedio} kW.')

Lo anterior resulta en

El promedio anual es 1.01 kW.

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

El promedio de enero es 1.06 kW.

Tercero, la energía consumida en enero es la integral (calculada sobre el


tiempo) de la potencia de dicho mes. Una forma de aproximar dicha integral
es calcular
horas_enero - 1
p_enero[i] *dt
X

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

Nótese que las unidades de la energía son kW h. Lo anterior resulta en

La energía consumida en enero es de 785.0 kWh.

Cuarto y último, las potencias mínima y máxima consumidas durante el año


se obtienen con las funciones min y max.

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

Potencias mínima y máxima: 0.39 kW y 3.11 kW.

9 La forma más directa es aprovechar las rebanadas, que permiten obtener


sublistas:

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

[0.9997224717845197, 1.0020052224259404, 0.9998839897250388]

10 La forma más directa es convertir la lista meters a un conjunto, en el


cual no existen los duplicados. Después, simplemente se toma su longitud:

N = len(set(meters))
print(f'La cantidad de modelos únicos es {N}.')

El resultado es

La cantidad de modelos únicos es 99.

11 Este es un problema que se deja resolver de una forma particularmente


elegante:

meters = file2list('data/meters.txt', type=str)


meters = [meter.replace('AM', 'AMI') for meter in meters]

Al escribir print(meters[:3]) se verifica que los modelos fueron corregidos:

['AMI158', 'AMI107', 'AMI108']

Esta solución aprovecha tres características del lenguaje: el método replace


de las cadenas, la comprensión de listas y el hecho de que una variable pueda
ser modificada en función de sí misma: el lado derecho depende de meters,
que es precisamente la variable que está siendo modificada.

42

También podría gustarte