Next Up Previous Hi Index

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}


Next Up Previous Hi Index