Clase 5: Persistencia de datos y administración de problemas
Escritura y lectura a archivos
Nuestros programas necesitan interactuar con el mundo exterior. Hasta
ahora utilizamos la función print()
para imprimir por pantalla
mensajes y resultados. Para leer o escribir un archivo primero debemos
abrirlo, utilizando la función open()
f = open('../data/names.txt') # Abrimos el archivo (para leer)
f
<_io.TextIOWrapper name='../data/names.txt' mode='r' encoding='UTF-8'>
s = f.read() # Leemos el archivo
f.close() # Cerramos el archivo
print(s[:100])
Aaa
Aaron
Aba
Ababa
Ada
Ada
Adam
Adlai
Adrian
Adrienne
Agatha
Agnetha
Ahmed
Ahmet
Aimee
Al
Ala
Alain
Esta secuencia básica de trabajo en adecuada y muy común en el trabajo con archivos. Sin embargo, hay un potencial problema, que ocurrirá si hay algún error entre la apertura y el cierre del archivo. Para ello existe una sintaxis alternativa
with open('../data/names.txt') as fi:
s = fi.read()
print(s[:50])
Aaa
Aaron
Aba
Ababa
Ada
Ada
Adam
Adlai
Adrian
Adri
# fi todavía existe pero está cerrado
fi
<_io.TextIOWrapper name='../data/names.txt' mode='r' encoding='UTF-8'>
type(fi)
_io.TextIOWrapper
La palabra with
es una palabra reservada del lenguaje y la
construcción se conoce como contexto. Básicamente dice que todo lo que
está dentro del bloque se realizará en el contexto en que f
es el
objeto de archivo abierto para lectura.
Ejemplos
Vamos a repasar algunos de los conceptos discutidos las clases anteriores e introducir algunas nuevas funcionalidades con ejemplos
Ejemplo 05-1
fname = '../data/names.txt'
n = 0 # contador
minlen = 3 # longitud mínima
maxlen = 4 # longitud máxima
with open(fname, 'r') as fi:
lines = fi.readlines() # El resultado es una lista
for line in lines:
if minlen <= len(line.strip()) <= maxlen:
n += 1
print(line.strip(), end=', ') # No Newline
print('\n')
if minlen == maxlen:
mensaje = f"Encontramos {n} palabras que tienen {minlen} letras"
else:
mensaje = f"Encontramos {n} palabras que tienen entre {minlen} y {maxlen} letras"
print(mensaje)
Aaa, Aba, Ada, Ada, Adam, Ala, Alan, Alex, Alf, Ama, Ami, Amir, Amos, Amy, Ana, Andy, Ann, Anna, Anna, Anne, Anya, Arne, Art, Axel, Bart, Bea, Ben, Bert, Beth, Bib, Bill, Bob, Bob, Boob, Boyd, Brad, Bret, Bub, Buck, Bud, Carl, Cary, Case, Cdc, Chet, Chip, Clay, Clem, Cody, Cole, Cory, Cris, Curt, Dad, Dale, Dan, Dana, Dani, Dave, Dawn, Dean, Deb, Debi, Deed, Del, Dick, Did, Dion, Dirk, Dod, Don, Donn, Dora, Dori, Dory, Doug, Drew, Dud, Duke, Earl, Eddy, Eke, Eli, Elsa, Emil, Emma, Enya, Ere, Eric, Erik, Esme, Eva, Evan, Eve, Eve, Ewe, Eye, Fay, Fred, Gag, Gaia, Gail, Gale, Gary, Gay, Gene, Gig, Gigi, Gil, Gill, Glen, Gog, Greg, Guy, Hal, Hank, Hans, Harv, Hein, Herb, Hohn, Hon, Hope, Hsi, Huey, Hugh, Huh, Hui, Hume, Hurf, Hwa, Iain, Ian, Igor, Iii, Ilya, Ima, Imad, Ira, Isis, Izzy, Jack, Jade, Jan, Jane, Jarl, Jay, Jean, Jef, Jeff, Jem, Jen, Jenn, Jess, Jill, Jim, Jin, Jiri, Joan, Job, Jock, Joe, Joel, John, Jon, Jong, Joni, Joon, Jos, Jose, Josh, Juan, Judy, Juha, Jun, June, Juri, Kaj, Kari, Karl, Kate, Kay, Kee, Kees, Ken, Kenn, Kent, Kiki, Kim, King, Kirk, Kit, Knut, Kory, Kris, Kurt, Kyle, Kylo, Kyu, Lana, Lar, Lara, Lars, Lea, Leah, Lee, Leif, Len, Leo, Leon, Les, Lex, Liam, Lila, Lin, Lisa, List, Liz, Liza, Lois, Lola, Lord, Lori, Lou, Loyd, Luc, Lucy, Lui, Luis, Luke, Lum, Lynn, Mac, Mah, Mann, Mara, Marc, Mark, Mary, Mat, Mats, Matt, Max, May, Mayo, Meg, Mick, Miek, Mike, Miki, Milo, Moe, Mott, Mum, Mwa, Naim, Nan, Nate, Neal, Ned, Neil, Nhan, Nici, Nick, Nils, Ning, Noam, Noel, Non, Noon, Nora, Norm, Nou, Novo, Nun, Ofer, Olaf, Old, Ole, Oleg, Olof, Omar, Otto, Owen, Ozan, Page, Pam, Pap, Part, Pat, Paul, Pdp, Peep, Pep, Per, Pete, Petr, Phil, Pia, Piet, Pim, Ping, Pip, Poop, Pop, Pria, Pup, Raif, Raj, Raja, Ralf, Ram, Rand, Raul, Ravi, Ray, Real, Rees, Reg, Reid, Rene, Renu, Rex, Ric, Rich, Rick, Rik, Rob, Rod, Rolf, Ron, Root, Rose, Ross, Roy, Rudy, Russ, Ruth, S's, Saad, Sal, Sam, Sara, Saul, Scot, Sean, Sees, Seth, Shai, Shaw, Shel, Sho, Sid, Sir, Sis, Skef, Skip, Son, Spy, Sri, Ssi, Stan, Stu, Sue, Sus, Suu, Syd, Syed, Syun, Tad, Tai, Tait, Tal, Tao, Tara, Tat, Ted, Teet, Teri, Tex, Thad, The, Theo, Tim, Timo, Tip, Tit, Tnt, Toby, Todd, Toft, Tom, Tony, Toot, Tor, Tot, Tran, Trey, Troy, Tuan, Tuna, Uma, Una, Uri, Urs, Val, Van, Vern, Vic, Vice, Vick, Wade, Walt, Wes, Will, Win, Wolf, Wow, Zoe, Zon,
Encontramos 420 palabras que tienen entre 3 y 4 letras
Hemos utilizado aquí:
Apertura, lectura, y cerrado de archivos
Iteración en un loop
for
Bloques condicionales (if/else)
Formato de cadenas de caracteres con reemplazo
Impresión por pantalla
La apertura de archivos se realiza utilizando la función open
(este
es un buen momento para mirar su documentación) con dos argumentos: el
primero es el nombre del archivo y el segundo el modo en que queremos
abrirlo (en este caso la r
indica lectura).
Con el archivo abierto, en la línea 9 leemos línea por línea todo el archivo. El resultado es una lista, donde cada elemento es una línea.
Recorremos la lista, y en cada elemento comparamos la longitud de la línea con ciertos valores. Imprimimos las líneas seleccionadas
Finalmente, escribimos el número total de líneas.
Veamos una leve modificación de este programa
Ejemplo 05-2
lines[0]
'Aaan'
"""Programa para contar e imprimir las palabras de una longitud dada"""
fname = '../data/names.txt'
n = 0 # contador
minlen = 3 # longitud mínima
maxlen = 4 # longitud máxima
with open(fname, 'r') as fi:
for line in fi:
p = line.strip().lower()
if (minlen <= len(p) <= maxlen) and (p == p[::-1]):
n += 1
print('({:02d}): {}'.format(n, p), end=', ') # Vamos numerando las coincidencias
print('\n')
if minlen == maxlen:
mensaje = f"Encontramos un total de {n} palabras capicúa que tienen {minlen} letras"
else:
mensaje = f"Encontramos un total de {n} palabras capicúa que tienen entre {minlen} y {maxlen} letras"
print(mensaje)
(01): aaa, (02): aba, (03): ada, (04): ada, (05): ala, (06): ama, (07): ana, (08): anna, (09): anna, (10): bib, (11): bob, (12): bob, (13): boob, (14): bub, (15): cdc, (16): dad, (17): deed, (18): did, (19): dod, (20): dud, (21): eke, (22): ere, (23): eve, (24): eve, (25): ewe, (26): eye, (27): gag, (28): gig, (29): gog, (30): huh, (31): iii, (32): mum, (33): nan, (34): non, (35): noon, (36): nun, (37): otto, (38): pap, (39): pdp, (40): peep, (41): pep, (42): pip, (43): poop, (44): pop, (45): pup, (46): s's, (47): sees, (48): sis, (49): sus, (50): tat, (51): teet, (52): tit, (53): tnt, (54): toot, (55): tot, (56): wow,
Encontramos un total de 56 palabras capicúa que tienen entre 3 y 4 letras
Aquí en lugar de leer todas las líneas e iterar sobre las líneas resultantes, iteramos directamente sobre el archivo abierto.
Además incluimos un string al principio del archivo, que servirá de documentación, y puede accederse mediante los mecanismos usuales de ayuda de Python.
Imprimimos el número de palabra junto con la palabra, usamos 02d
,
indicando que es un entero (d
), que queremos que el campo sea de un
mínimo número de caracteres de ancho (en este caso 2). Al escribirlo
como 02
le pedimos que complete los vacíos con ceros.
"""Programa para contar e imprimir las palabras de una longitud dada"""
fname = '../data/names.txt'
n = 0 # contador
minlen = 3 # longitud mínima
maxlen = 4 # longitud máxima
L = []
with open(fname, 'r') as fi:
for line in fi:
p = line.strip().lower()
if (minlen <= len(p) <= maxlen) and (p == p[::-1]):
n += 1
#ss += f"\n{p}" # ss += "\n" + p
L.append(p) # L += [p]
ss = " ".join(L)
if minlen == maxlen:
mensaje = f"Encontramos un total de {n} palabras capicúa que tienen {minlen} letras"
else:
mensaje = f"Encontramos un total de {n} palabras capicúa que tienen entre {minlen} y {maxlen} letras"
print(mensaje)
with open('../data/tmp.txt','w') as fo:
fo.write(ss)
Encontramos un total de 56 palabras capicúa que tienen entre 3 y 4 letras
Archivos comprimidos
Existen varias formas de reducir el tamaño de los archivos de datos. Varios factores, tales como el sistema operativo, nuestra familiaridad con cada uno de ellos, le da una cierta preferencia a algunos de los métodos disponibles. Veamos cómo hacer para leer y escribir algunos de los siguientes formatos: zip, gzip, bz2
import gzip
import bz2
with gzip.open('../data/palabras.words.gz', 'rb') as fi:
a = fi.read()
with gzip.open('../data/palabras.words.gz', 'r') as fi:
b = fi.read()
b[:30]
b'xc3x81fricanxc3x81ngelanxc3xa1baconxc3xa1bsida'
l= a.splitlines()
print(l[:10])
[b'xc3x81frica', b'xc3x81ngela', b'xc3xa1baco', b'xc3xa1bsida', b'xc3xa1bside', b'xc3xa1cana', b'xc3xa1caro', b'xc3xa1cates', b'xc3xa1cido', b'xc3xa1cigos']
a[:30]
b'xc3x81fricanxc3x81ngelanxc3xa1baconxc3xa1bsida'
str(l[0])
"b'\xc3\x81frica'"
type(l[0])
bytes
Nota
Vemos que el archivo tiene algunos caracteres que no podemos interpretar. Por ejemplo:
l[0] = "b'\\xc3\\x81frica'"
l[0] = str(l[0])
Esto indica que la variable es del tipo “bytes”, que es la manera en que python describe los strings, pero hay un caracter que no sabemos como mostrar. Para hacerlo debemos codificarlo:
str(l[0], encoding='utf-8') -> 'África'
str(l[0], encoding='utf-8')
'África'
Con todo esto podríamos escribir (si tuviéramos necesidad) una función que puede leer un archivo en cualquiera de estos formatos
import gzip
import bz2
from os.path import splitext
import zipfile
def abrir(fname, modo='r'):
if fname.endswith('gz'):
fi= gzip.open(fname, mode=modo)
elif fname.endswith('bz2'):
fi= bz2.open(fname, mode=modo)
elif fname.endswith('zip'):
fi= zipfile.ZipFile(fname, mode=modo)
else:
fi = open(fname, mode=modo)
return fi
ff= abrir('../data/palabras.words.gz')
a = ff.read()
ff.close()
l = a.splitlines()
print(str(l[0], encoding='utf-8'))
África
Ejercicios 05 (a)
Realice un programa que:
Lea el archivo names.txt
Guarde en un nuevo archivo (llamado “pares.txt”) palabra por medio del archivo original (la primera, tercera, …) una por línea, pero en el orden inverso al leído
Agregue al final de dicho archivo, las palabras pares pero separadas por un punto y coma (;)
En un archivo llamado “longitudes.txt” guarde las palabras ordenadas por su longitud, y para cada longitud ordenadas alfabéticamente.
En un archivo llamado “letras.txt” guarde sólo aquellas palabras que contienen las letras
w,x,y,z
, con el formato:w: Walter, ….
x: Xilofón, …
y: ….
z: ….
Cree un diccionario, donde cada key es la primera letra y cada valor es una lista, cuyo elemento es una tuple (palabra, longitud). Por ejemplo:
d['a'] = [('Aaa',3),('Anna', 4), ...]
Realice un programa para:
Leer los datos del archivo aluminio.dat y poner los datos del elemento en un diccionario de la forma:
d = {'S': 'Al', 'Z':13, 'A':27, 'M': '26.98153863(12)', 'P': 1.0000, 'MS':'26.9815386(8)'}
Modifique el programa anterior para que las masas sean números (
float
) y descarte el valor de la incerteza (el número entre paréntesis)Agregue el código necesario para obtener una impresión de la forma:
Elemento: Al Número Atómico: 13 Número de Masa: 27 Masa: 26.98154
Note que la masa sólo debe contener 5 números decimales
Nota
Los archivos de texto “names.txt” y “aluminio.txt” (así como otros archivos usados en las clases) pueden encontrarse en la carpeta intro-python
Atrapar y administrar errores
Python tiene incorporado un mecanismo para atrapar errores de distintos tipos, así como para generar errores que den información al usuario sobre usos incorrectos del código.
En primer lugar consideremos lo que se llama un error de sintaxis. El siguiente comando es sintácticamente correcto y el intérprete sabe como leerlo
print("hola")
hola
mientras que, si escribimos algo que no está permitido en el lenguaje
print("hola"))
Cell In[2], line 1
print("hola"))
^
SyntaxError: unmatched ')'
El intérprete detecta el error y repite la línea donde lo identifica. Este tipo de errores debe corregirse para poder seguir con el programa.
Consideremos ahora el código siguiente, que es sintácticamente correcto pero igualmente causa un error
a = 1
b = 0
z = a / b
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[3], line 3
1 a = 1
2 b = 0
----> 3 z = a / b
ZeroDivisionError: division by zero
Cuando se encuentra un error, Python muestra el lugar en que ocurre y de qué tipo de error se trata.
print(hola)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 1
----> 1 print(hola)
NameError: name 'hola' is not defined
Este mensaje da un tipo de error diferente. Ambos: ZeroDivisionError
y NameError
son tipos de errores (o excepciones). Hay una larga
lista de tipos de errores que son parte del lenguaje y puede consultarse
en la documentación de Built-in
Exceptions.
Administración de excepciones
Cuando nuestro programa aumenta en complejidad, aumenta la posibilidad de encontrar errores. Esto se incrementa si se tiene que interactuar con otros usuarios o con datos externos. Consideremos el siguiente ejemplo simple:
with open("../data/ej_clase5.dat") as fi:
for l in fi:
t = l.split()
print("t = {}".format(t)) # Línea sólo para inspección
m = int(t[0])
n = int(t[1])
print(f"m = {m}, n = {n}, m x n = {m*n}")
print("Seguimos")
t = ['1', '2']
m = 1, n = 2, m x n = 2
t = ['2', '6']
m = 2, n = 6, m x n = 12
t = ['3', '9']
m = 3, n = 9, m x n = 27
t = ['4', '12']
m = 4, n = 12, m x n = 48
t = ['5.5', '30.25']
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[5], line 5
3 t = l.split()
4 print("t = {}".format(t)) # Línea sólo para inspección
----> 5 m = int(t[0])
6 n = int(t[1])
7 print(f"m = {m}, n = {n}, m x n = {m*n}")
ValueError: invalid literal for int() with base 10: '5.5'
En este caso se “levanta” una excepción del tipo ValueError
debido a
que este valor (5.5
) no se puede convertir a int
. Podemos
modificar nuestro programa para manejar este error:
with open("../data/ej_clase5.dat") as fi:
for l in fi:
t = l.split()
try:
m = int(t[0])
n = int(t[1])
print(f"m = {m}, n = {n}, m x n = {m*n}")
except:
print(f"Error: t = {t} no puede convertirse a entero")
print("Seguimos")
m = 1, n = 2, m x n = 2
m = 2, n = 6, m x n = 12
m = 3, n = 9, m x n = 27
m = 4, n = 12, m x n = 48
Error: t = ['5.5', '30.25'] no puede convertirse a entero
m = 3, n = 9, m x n = 27
Error: t = ['2'] no puede convertirse a entero
Seguimos
En este caso podríamos ser más precisos y especificar el tipo de excepción que estamos esperando
with open("../data/ej_clase5.dat") as fi:
for l in fi:
t = l.split()
try:
m = int(t[0])
n = int(t[1])
print(f"m = {m}, n = {n}, m x n = {m*n}")
except(ValueError):
print(f"Error: t = {t} no puede convertirse a entero")
m = 1, n = 2, m x n = 2
m = 2, n = 6, m x n = 12
m = 3, n = 9, m x n = 27
m = 4, n = 12, m x n = 48
Error: t = ['5.5', '30.25'] no puede convertirse a entero
m = 3, n = 9, m x n = 27
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[12], line 6
4 try:
5 m = int(t[0])
----> 6 n = int(t[1])
7 print(f"m = {m}, n = {n}, m x n = {m*n}")
8 except(ValueError):
IndexError: list index out of range
with open("../data/ej_clase5.dat") as fi:
for l in fi:
t = l.split()
try:
m = int(t[0])
n = int(t[1])
print(f"m = {m}, n = {n}, m x n = {m*n}")
except(ValueError):
print(f"Error: t = {t} no puede convertirse a entero")
except(IndexError):
print(f'Error: La línea "{l.strip()}" no contiene un par')
print("Seguimos...")
m = 1, n = 2, m x n = 2
m = 2, n = 6, m x n = 12
m = 3, n = 9, m x n = 27
m = 4, n = 12, m x n = 48
Error: t = ['5.5', '30.25'] no puede convertirse a entero
m = 3, n = 9, m x n = 27
Error: La línea "2" no contiene un par
Seguimos...
with open("../data/ej_clase5.dat") as fi:
for l in fi:
t = l.split()
try:
m = int(t[0])
n = int(t[1])
print("m = {}, n = {}, m x n = {}".format(m,n, m*n))
except(ValueError):
print("Error: t = {} no puede convertirse a entero".format(t))
except:
print('Error: La línea "{}" tiene otro error'.format(l.strip()))
print("Seguimos...")
m = 1, n = 2, m x n = 2
m = 2, n = 6, m x n = 12
m = 3, n = 9, m x n = 27
m = 4, n = 12, m x n = 48
Error: t = ['5.5', '30.25'] no puede convertirse a entero
m = 3, n = 9, m x n = 27
Error: La línea "2" tiene otro error
Seguimos...
La forma general
La declaración try
funciona de la siguiente manera:
Primero, se ejecuta el bloque try (el código entre las declaración
try
yexcept
).Si no ocurre ninguna excepción, el bloque except se saltea y termina la ejecución de la declaración
try
.Si ocurre una excepción durante la ejecución del bloque try, el resto del bloque se saltea. Luego, si su tipo coincide con la excepción nombrada luego de la palabra reservada
except
, se ejecuta el bloque except, y la ejecución continúa luego de la declaracióntry
.Si ocurre una excepción que no coincide con la excepción nombrada en el
except
, esta se pasa a declaracionestry
de más afuera; si no se encuentra nada que la maneje, es una excepción no manejada, y la ejecución se frena con un mensaje como los mostrados arriba.
El mecanismo es un poco más complejo, y permite un control más fino que lo descripto aquí.
“Crear” excepciones
Podemos forzar a que nuestro código cree una excepción usando raise
.
Por ejemplo:
import math
def mi_sqrt(x):
if x < 0:
raise ValueError(f"x = {x}, debería ser positivo")
return math.sqrt(x)
mi_sqrt(12)
3.4641016151377544
mi_sqrt(-2)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[19], line 1
----> 1 mi_sqrt(-2)
Cell In[17], line 4, in mi_sqrt(x)
2 def mi_sqrt(x):
3 if x < 0:
----> 4 raise ValueError(f"x = {x}, debería ser positivo")
5 return math.sqrt(x)
ValueError: x = -2, debería ser positivo
mi_sqrt(1+2j)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[20], line 1
----> 1 mi_sqrt(1+2j)
Cell In[17], line 3, in mi_sqrt(x)
2 def mi_sqrt(x):
----> 3 if x < 0:
4 raise ValueError(f"x = {x}, debería ser positivo")
5 return math.sqrt(x)
TypeError: '<' not supported between instances of 'complex' and 'int'