Chapter 11
Archivos y excepciones
Cuando un programa se está ejecutando, sus datos están en la memoria. Cuando
un programa termina, o se apaga el computador, los datos de la memoria
desaparecen. Para almacenar los datos de forma permanente debe usted ponerlos
en un archivo. Normalmente los archivos se guardan en un disco duro,
disquete o CD-ROM.
Cuando hay un gran número de archivos, suelen estar organizados en
directorios (también llamados "carpetas"). Cada archivo se
identifica con un nombre único, o una combinación de nombre de archivo
y nombre de directorio.
Leyendo y escribiendo archivos, los programas pueden intercambiar
información entre ellos y generar formatos imprimibles como PDF.
Trabajar con archivos se parece mucho a trabajar con libros. Para usar
un libro, tiene que abrirlo. Cuando ha terminado, tiene que cerrarlo.
Mientras el libro está abierto, puede escribir en él o leer de él.
En cualquier caso, sabe en qué lugar del libro se encuentra. Casi
siempre lee el libro según su orden natural, pero también puede
ir saltando de página en página.
Todo esto sirve también para los archivos. Para abrir un archivo,
especifica su nombre e indica si quieres leer o escribir.
La apertura de un archivo crea un objeto archivo. En este ejemplo, la
variable f apunta al nuevo objeto archivo.
\begin{verbatim
>>> f = open("test.dat","w")
>>> print f
\end{verbatim}
\afterverb
%
La función open toma dos argumentos. El primero es el nombre del archivo
y el segundo es el modo. El modo {\tt 'w'} significa que lo estamos abriendo
para escribir.
Si no hay un archivo llamado {\tt test.dat} se creará. Si ya hay uno, el
archivo que estamos escribiendo lo reemplazará.
Al imprimir el objeto archivo, vemos el nombre del archivo, el
modo y la localización del objeto.
Para meter datos en el archivo invocamos al método {\tt write}
sobre el objeto archivo:
\beforeverb
\begin{verbatim}
>>> f.write("Ya es hora")
>>> f.write("de cerrar el archivo")
\end{verbatim}
\afterverb
%
El cierre del archivo le dice al sistema que hemos terminado de
escribir y deja el archivo listo para leer:
\beforeverb
\begin{verbatim}
>>> f.close()
\end{verbatim}
\afterverb
%
Ya podemos abrir el archivo de nuevo, esta vez para lectura, y poner su
contenido en una cadena. Esta vez el argumento de modo es {\tt 'r'} para
lectura:
\beforeverb
\begin{verbatim}
>>> f = open("test.dat","r")
\end{verbatim}
\afterverb
%
Si intentamos abrir un archivo que no existe, recibimos un mensaje de error:
\index{error en tiempo de ejecución}
\beforeverb
\begin{verbatim}
>>> f = open("test.cat","r")
IOError: [Errno 2] No such file or directory: 'test.cat'
\end{verbatim}
\afterverb
%
Como era de esperar, el método {\tt read} lee datos del archivo. Sin
argumentos, lee el archivo completo:
\beforeverb
\begin{verbatim}
>>> text = f.read()
>>> print text
Ya es horade cerrar el archivo
\end{verbatim}
\afterverb
%
No hay un espacio entre ``hora'' y ``de'' porque no escribimos un espacio
entre las cadenas.
{\tt read} también puede aceptar un argumento que le indica cuántos
caracteres leer:
\beforeverb
\begin{verbatim}
>>> f = open("test.dat","r")
>>> print f.read(7)
Ya es h
\end{verbatim}
\afterverb
%
Si no quedan suficientes caracteres en el archivo, {\tt read} devuelve los que
haya. Cuando llegamos al final del archivo, {\tt read} devuelve una cadena vacía:
\beforeverb
\begin{verbatim}
>>> print f.read(1000006)
orade cerrar el archivo
>>> print f.read()
>>>
\end{verbatim}
\afterverb
%
La siguiente función copia un archivo, leyendo y escribiendo los
caracteres de cincuenta en cincuenta. El primer argumento es el
nombre del archivo original; el segundo es el nombre del archivo nuevo:
\beforeverb
\begin{verbatim}
def copiaArchivo(archViejo, archNuevo):
f1 = open(archViejo, "r")
f2 = open(archNuevo, "w")
while 1:
texto = f1.read(50)
if texto == "":
break
f2.write(texto)
f1.close()
f2.close()
return
\end{verbatim}
\afterverb
%
La sentencia {\tt break} es nueva. Su ejecución interrumpe el
bucle; el flujo de la ejecución pasa a la primera sentencia tras
el bucle.
\index{sentencia break}
\index{sentencia!break}
En este ejmplo, el bucle {\tt while} es infinito porque el valor
{\tt 1} siempre es verdadero. La {\em única} forma de salir del
bucle es ejecutar {\tt break}, lo que sucede cuando {\tt texto}
es una cadena vacía, lo que sucede cuando llegamos al final del
archivo.
\section{Archivos de texto}
\index{archivo de texto}
\index{archivo!texto}
Un {\bf archivo de texto} es un archivo que contiene caracteres imprimibles
y espacios organizados en líneas separadas por caracteres de salto de línea.
Como Python está diseñado específicamente para procesar archivos de texto,
proporciona métodos que facilitan la tarea.
Para hacer una demostración, crearemos un archivo de texto con tres líneas de
texto separadas por saltos de línea:
\beforeverb
\begin{verbatim}
>>> f = open("test.dat","w")
>>> f.write("línea uno\nlínea dos\nlínea tres\n")
>>> f.close()
\end{verbatim}
\afterverb
%
El método {\tt readline} lee todos los caracteres hasta e incluyendo el
siguiente salto de línea:
\beforeverb
\begin{verbatim}
>>> f = open("test.dat","r")
>>> print f.readline()
línea uno
>>>
\end{verbatim}
\afterverb
%
{\tt readlines} devuelve todas las líneas que queden como una lista de
cadenas:
\beforeverb
\begin{verbatim}
>>> print f.readlines()
['línea dos\012', 'línea tres\012']
\end{verbatim}
\afterverb
%
En este caso, la salida está en forma de lista, lo que significa que
las cadenas aparecen con comillas y el carácter de salto de línea
aparece como la secuencia de escape {\tt \\012}.
Al final del archivo, {\tt readline} devuelve una cadena vacía y
{\tt readlines} devuelve una lista vacía:
\beforeverb
\begin{verbatim}
>>> print f.readline()
>>> print f.readlines()
[]
\end{verbatim}
\afterverb
%
Lo que sigue es un ejemplo de un programa de proceso de líneas.
{\tt filtraArchivo} hace una copia de {\tt archViejo}, omitiendo
las líneas que comienzan por {\tt \#}:
\beforeverb
\begin{verbatim}
def filtraArchivo(archViejo, archNuevo):
f1 = open(archViejo, "r")
f2 = open(archNuevo, "w")
while 1:
texto = f1.readline()
if texto == "":
break
if texto[0] == '#':
continue
f2.write(texto)
f1.close()
f2.close()
return
\end{verbatim}
\afterverb
%
La sentencia {\tt continue} termina la iteración actual del bucle,
pero sigue haciendo bucles. El flujo de ejecución pasa al principio
del bucle, comprueba la condición y continúa en consecuencia.
\index{sentencia continue}
\index{sentencia!continue}
Así, si {\tt texto} es una cadena vacía, el bucle termina. Si el primer
carácter de {\tt texto} es una almohadilla, el flujo de ejecución va al
principio del bucle. Sólo si ambas condiciones fallan copiamos {\tt texto}
en el archivo nuevo.
\section{Escribir variables}
\index{operador de formato}
\index{cadena de formato}
\index{operador!formato}
El argumento de {\tt write} debe ser una cadena, así que si queremos
poner otros valores en un archivo, tenemos que convertirlos antes en
cadenas. La forma más fácil de hacerlo es con la función {\tt str}:
\beforeverb
\begin{verbatim}
>>> x = 52
>>> f.write (str(x))
\end{verbatim}
\afterverb
%
Una alternativa es usar el {\bf operador de formato} {\tt \%}. Cuando
aplica a enteros, {\tt \%} es el operador de módulo. Pero cuando el
primer operando es una cadena, {\tt \%} es el operador de formato.
El primer operando es la {\bf cadena de formato}, y el segundo operando
es una tupla de expresiones. El resultado es una cadena que contiene los
valores de las expresiones, formateados de acuerdo a la cadena de formato.
A modo de ejemplo simple, la {\bf secuencia de formato} {\tt '\%d'}
significa que la primera expresión de la tupla debería formatearse como
un entero. Aquí la letra {\em d} quiere decir ``decimal'':
\beforeverb
\begin{verbatim}
>>> motos = 52
>>> "%d" % motos
'52'
\end{verbatim}
\afterverb
%
El resultado es la cadena {\tt '52'}, que no debe confundirse con el valor
entero {\tt 52}.
Una secuencia de formato puede aparecer en cualquier lugar de la cadena de
formato, de modo que podemos incrustar un valor en una frase:
\beforeverb
\begin{verbatim}
>>> motos = 52
>>> "En julio vendimos %d motos." % motos
'En julio vendimos 52 motos.'
\end{verbatim}
\afterverb
%
La secuencia de formato {\tt '\%f'} formatea el siguiente elemento
de la tupla como un número en coma flotante, y {\tt '\%s'} formatea
el siguiente elemento como una cadena:
\beforeverb
\begin{verbatim}
>>> "En %d días ingresamos %f millones de %s." % (34,6.1,'dólares')
'En 34 días ingresamose 6.100000 miliones de dólares.'
\end{verbatim}
\afterverb
%
Por defecto, el formato de coma flotante imprime seis decimales.
El número de expresiones en la tupla tiene que coincidir con el número
de secuencias de formato de la cadena. Igualmente, los tipos de las
expresiones deben coincidir con las secuencias de formato:
\index{error en tiempo de ejecución}
\beforeverb
\begin{verbatim}
>>> "%d %d %d" % (1,2)
TypeError: not enough arguments for format string
>>> "%d" % 'dólares'
TypeError: illegal argument type for built-in operation
\end{verbatim}
\afterverb
%
En el primer ejemplo, no hay suficientes expresiones; en el
segundo, la expresión es de un tipo incorrecto.
Para tener más control sobre el formato de los números, podemos detallar
el número de dígitos como parte de la secuencia de formato:
\beforeverb
\begin{verbatim}
>>> "%6d" % 62
' 62'
>>> "%12f" % 6.1
' 6.100000'
\end{verbatim}
\afterverb
%
El número tras el signo de porcentaje es el número mínimo de espacios
que ocupará el número. Si el valor necesita menos dígitos, se añaden
espacios en blanco delante del número. Si el número de espacios es
negativo, se añaden los espacios tras el número:
\beforeverb
\begin{verbatim}
>>> "%-6d" % 62
'62 '
\end{verbatim}
\afterverb
%
También podemos especificar el número de decimales para los
números en coma flotante:
\beforeverb
\begin{verbatim}
>>> "%12.2f" % 6.1
' 6.10'
\end{verbatim}
\afterverb
%
En este ejemplo, el resultado ocupa doce espacios e incluye dos dígitos
tras la coma. Este formato es útil para imprimir cantidades de dinero con
las comas alineadas.
\index{diccionario}
Imagina, por ejemplo, un diccionario que contiene los nombres
de los estudiantes como clave y las tarifas horarias como valores.
He aquí una función que imprime el contenido del diccionario como
de un informe formateado:
\beforeverb
\begin{verbatim}
def informe (tarifas) :
estudiantes = tarifas.keys()
estudiantes.sort()
for estudiante in estudiantes :
print "%-20s %12.02f" % (estudiante, tarifas[estudiante])
\end{verbatim}
\afterverb
%
Para probar la función, crearemos un pequeño diccionario e
imprimiremos el contenido:
\beforeverb
\begin{verbatim}
>>> tarifas = {'maría': 6.23, 'josé': 5.45, 'jesús': 4.25}
>>> informe (tarifas)
josé 5.45
jesús 4.25
maría 6.23
\end{verbatim}
\afterverb
%
Controlando la anchura de cada valor nos aseguramos de que las columnas
van a quedar alineadas, siempre que los nombres tengan menos de veintiún
caracteres y las tarifas sean menos de mil millones la hora.
\section{Directorios}
\index{directorio}
Cuando usted crea un archivo nuevo abriéndolo y escribiendo, el nuevo
archivo va al directorio en uso (aquél en el que etuvieses al ejecutar
el programa). Del mismo modo, cuando abre un archivo para leerlo,
Python lo busca en el directorio en uso.
Si queire abrir un archivo de cualquier otro sitio, tiene que
especificar la {\bf ruta} del archivo, que es el nombre del direcotrio
(o carpeta) donde se encuentra éste:
\beforeverb
\begin{verbatim}
>>> f = open("/usr/share/dict/words","r")
>>> print f.readline()
Aarhus
\end{verbatim}
\afterverb
%
Este ejemplo abre un archivo llamado {\tt words} que está en un
directorio llamado {\tt dict}, que está en {\tt share}, que está
en {\tt usr}, que está en el directorio de nivel superior del
sistema, llamado {\tt /}.
\index{ruta}
\index{delimitador}
No puede usar {\tt /} como parte del nombre de un archivo; está
reservado como delimitador entre nombres de archivo y directorios.
El archivo {\tt /usr/share/dict/words} contiene una lista de palabras
en orden alfabético, la primera de las cuales es el nombre de una
universidad danesa.
\section{Encurtido}
\index{encurtido}
Para poner valores en un archivo, debe convertirlos en cadenas. Ya
ha visto cómo hacerlo con {\tt str}:
\beforeverb
\begin{verbatim}
>>> f.write (str(12.3))
>>> f.write (str([1,2,3]))
\end{verbatim}
\afterverb
%
El problema es que cuando vuelve usted a leer el valor, obtiene una cadena.
Ha perdido la información del tipo de dato original. En realidad, no
puede distinguir dónde termina un valor y comienza el siguiente:
\beforeverb
\begin{verbatim}
>>> f.readline()
'12.3[1, 2, 3]'
\end{verbatim}
\afterverb
%
La solución es el {\bf encurtido}, llamado así porque ``conserva''
estructuras de datos. El módulo {\tt pickle} contiene las órdenes
necesarias. Para usarlo, importe {\tt pickle} y luego abra el archivo
de la forma habitual:
\beforeverb
\begin{verbatim}
>>> import pickle
>>> f = open("test.pck","w")
\end{verbatim}
\afterverb
%
Para almacenar una estructura de datos, use el métido{\tt dump}
y luego cierre el archivo de la forma habitual:
\beforeverb
\begin{verbatim}
>>> pickle.dump(12.3, f)
>>> pickle.dump([1,2,3], f)
>>> f.close()
\end{verbatim}
\afterverb
%
Ahora podemos abrir el archivo para leer y cargar las estructuras
de datos que volcamos ahí:
\beforeverb
\begin{verbatim}
>>> f = open("test.pck","r")
>>> x = pickle.load(f)
>>> x
12.3
>>> type(x)
>>> y = pickle.load(f)
>>> y
[1, 2, 3]
>>> type(y)
\end{verbatim}
\afterverb
%
Cada vez que invocamos {\tt load} obtenemos un valor del archivo,
completo con su tipo original.
\section{Excepciones}
\index{sentencia try}
\index{sentencia!try}
\index{lanzar una excepción}
\index{manejar una excepción}
\index{sentencia except}
\index{sentencia!except}
\index{excepción}
Siempre que ocurre un error en tiempo de ejecución, crea una
{\bf exception}. Normalmente el programa se para y Pythton
presenta un mensaje de error.
Por ejemplo, la división por cero crea una excepción:
\beforeverb
\begin{verbatim}
>>> print 55/0
ZeroDivisionError: integer division or modulo
\end{verbatim}
\afterverb
%
Un elemento no existente en una lista hace lo mismo:
\beforeverb
\begin{verbatim}
>>> a = []
>>> print a[5]
IndexError: list index out of range
\end{verbatim}
\afterverb
%
O el acceso a una clave que no está en el diccionario:
\beforeverb
\begin{verbatim}
>>> b = {}
>>> print b['qué']
KeyError: qué
\end{verbatim}
\afterverb
%
En cada caso, el mensaje de error tiene dos partes: el tipo
de error antes de los dos puntos y detalles sobre el error depués
de los dos puntos. Normalmente Python también imprime una traza de
dónde se encontraba el programa, pero la hemos omitido en los ejemplos.
\index{traza}
A veces queremos realizar una operación que podría provocar una
excepción, pero no queremos que se pare el programa. Podemos
{\bf manejar} la excepción usando las sentencias {\tt try} y
{\tt except}.
Por ejemplo, podemos preguntar al usuario por el nombre de un archivo
y luego intentar abrirlo. Si el archivo no existe, no queremos que el
programa se pare; queremos manejar la excepción.
\beforeverb
\begin{verbatim}
nombreArch = raw_input('Introduce un nombre de archivo: ')
try:
f = open (nombreArch, "r")
except:
print 'No hay ningún archivo que se llame', nombreArch
\end{verbatim}
\afterverb
%
La sentencia {\tt try} ejecuta las sentencias del primer bloque.
Si no se produce ninguna excepción, pasa por alto la sentencia
{\tt except}. Si ocurre cualquier excepción, ejecuta las sentencias
de la rama {\tt except} y después continua.
Podemos encapsular esta capacidad en una función: {\tt existe} acepta un
nombre de archivo y devuelve verdadero si el archivo existe y falso si no:
\beforeverb
\begin{verbatim}
def existe(nombreArch):
try:
f = open(nombreArch)
f.close()
return 1
except:
return 0
\end{verbatim}
\afterverb
%
Puedes usar múltiples bloques {\tt except} para manejar diferentes tipos
de excepciones. El {\em Manual de Referencia de Python} contiene los detalles.
Si su programa detecta una condición de error, puede hacer que lance
({\bf raise} en inglés) una excepción. Aquí tiene usted un ejemplo que acepta una entrada
del usuario y comprueba si es 17. Suponiendo que 17 no es una entrada válida
por cualquier razón, lanzamos una excepción.
\beforeverb
\begin{verbatim}
def tomaNumero () : # ¡Recuerde, los acentos están prohibidos
x = input ('Elige un número: ') # en los nombres de funciones y variables!
if x == 17 :
raise 'ErrorNúmeroMalo', '17 es un mal número'
return x
\end{verbatim}
\afterverb
%
La sentencia {\tt raise} acepta dos argumentos: el tipo de excepción
e información específica acerca del error. {\tt ErrorNúmeroMalo}
es un nuevo tipo de excepción que hemos inventado para esta aplicación.
Si la función llamada {\tt tomaNumero} maneja el error, el programa
puede continuar; en caso contrario, Python imprime el mensaje de error
y sale:
\beforeverb
\begin{verbatim}
>>> tomaNumero ()
Elige un número: 17
ErrorNúmeroMalo: 17 es un mal número
\end{verbatim}
\afterverb
%
El mensaje de error incluye el tipo de excepción y la información
adicional que usted proporcionó.
\begin{quote}
{\em Como ejercicio, escriba una función que use {\tt tomaNumero}
para leer un número del teclado y que maneje la excepción
{\tt ErrorNúmeroMalo}.}
\end{quote}
\section{Glosario}
\index{archivo}
\index{archivo de texto}
\index{sentencia break}
\index{sentencia!break}
\index{sentencia continue}
\index{sentencia!continue}
\index{operador de format}
\index{cadena de formato}
\index{operador!formato}
\index{directorio}
\index{encurtido}
\index{try}
\index{lanzar excepción}
\index{manejar excepción}
\index{sentencia except}
\index{excepción}
\begin{description}
\item[archivo:] Una entidad con nombre, normalmente almacenada en un disco
duro, disquete o CD-ROM, que contiene una secuencia de caracteres.
\item[directorio:] Una colección, con nombre, de archivos, también llamado carpeta.
\item[ruta:] Una secuencia de nombres de directorio que especifica la localización
exacta de un archivo.
\item[archivo de texto:] Un archivo que contiene caracteres imprimibles organizados
en líneas separadas por caracteres de salto de línea.
\item[sentencia break:] Una sentencia que provoca que el flujo de ejecución salga de
un bucle.
\item[sentencia continue:] Una sentencia que provoca que termine la
iteración actual de un bucle. El flujo de la ejecución va al principio
del bucle, evalúa la condición, y procede en consecuencia.
\item[operador de formato:] El operador {\tt \%} toma una cadena de
formato y una tupla de expresiones y entrega una cadena que incluye
las expresiones, formateadas de acuerdo con la cadena de formato.
\item[cadena de formato:] Una cadena que contiene caracteres imprimibles
y secuencias de formato que indican cómo formatear valores.
\item[secuencia de formato:] Una secuencia de caracteres que comienza con
{\tt \%} e indica cómo formatear un valor.
\item[encurtir:] Escribir el valor de un dato en un archivo junto con la
información sobre su tipo de forma que pueda ser reconstituido más tarde.
\item[excepción:] Un error que ocurre en tiempo de ejecución.
\item[manejar:] Impedir que una excepción detenga un programa utilizando
las sentencias {\tt try} y {\tt except}.
\item[lanzar:] Señalar una excepción usando la sentencia {\tt raise}.
\end{description}