Capítulo 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 una página a otra.

Todo esto sirve también para los archivos. Para abrir un archivo, especifique su nombre e indique si quiere leer o escribir.

La apertura de un archivo crea un objeto archivo. En este ejemplo, la variable f apunta al nuevo objeto archivo.

>>> f = open("test.dat","w")
>>> print f
<open file ’test.dat’, mode ’w’ at fe820>

La función open toma dos argumentos. El primero es el nombre del archivo y el segundo es el modo. El modo ’w’ (write) significa que lo estamos abriendo para escribir.

Si no hay un archivo llamado 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 write sobre el objeto archivo:

>>> f.write(“Ya es hora")
>>> f.write ("de cerrar el archivo")

El cierre del archivo le dice al sistema que hemos terminado de escribir y deja el archivo listo para leer:

>>> f.close()

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 ’r’ (read) para lectura:

>>> f = open("test.dat","r")

Si intentamos abrir un archivo que no existe, recibimos un mensaje de error:

>>> f = open("test.cat","r")
IOError: [Errno 2] No such file or directory: ’test.cat’

Como era de esperar, el método read lee datos del archivo. Sin argumentos, lee el archivo completo:

>>> text = f.read()
>>> print text
Ya es horade cerrar el archivo

No hay un espacio entre “hora” y “de” porque no escribimos un espacio entre las cadenas.

read también puede aceptar un argumento que le indica cuántos caracteres leer:

>>> f = open("test.dat", "r")
>>> print f.read(7)
Ya es h

Si no quedan suficientes caracteres en el archivo, read devuelve los que haya. Cuando llegamos al final del archivo, read devuelve una cadena vacía:

>>> print f.read(1000006)
orade cerrar el archivo
>>> print f.read()

>>>

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:

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

La sentencia break es nueva. Su ejecución interrumpe el bucle; el flujo de la ejecución pasa a la primera sentencia tras el bucle.

En este ejemplo, el bucle while es infinito porque el valor 1 siempre es verdadero. La única forma de salir del bucle es ejecutar break, lo que sucede cuando texto es una cadena vacía, lo que sucede cuando llegamos al final del archivo.

11.1. Archivos de texto

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

>>> f = open("test.dat","w")
>>> f.write("línea uno\nlínea dos\nlínea tres\n")
>>> f.close()

El método readline lee todos los caracteres hasta e inclusive el siguiente salto de línea:

>>> f = open("test .dat", "r")
>>> print f.readline()
línea uno

>>>

readlines devuelve todas las líneas que queden como una lista de cadenas:

>>> print f.readlines()
[’línea dos\012’, ’línea tres\012’]

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

012.

Al final del archivo, readline devuelve una cadena vacía y readlines devuelve una lista vacía:

>>> print f.readline()

>>> print f.readlines()
[]

Lo que sigue es un ejemplo de un programa de proceso de líneas, filtraArchivo hace una copia de archViejo, omitiendo las líneas que comienzan por #:

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

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

Así, si texto es una cadena vacía, el bucle termina. Si el primer carácter de texto es una almohadilla, el flujo de ejecución va al principio del bucle. Sólo si ambas condiciones fallan copiamos texto en el archivo nuevo.

11.2. Escribir variables

El argumento de 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 str:

>>> x = 52
>>> f.write(str(x))

Una alternativa es usar el operador de formato % Cuando aplica a enteros, % es el operador de módulo. Pero cuando el primer operando es una cadena, % es el operador de formato.

El primer operando es la 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 secuencia de formato ’%d’ significa que la primera expresión de la tupla debería formatearse como un entero. Aquí la letra d quiere decir “decimal”:

>>> motos = 52
>>> "%d" % motos
’52’

El resultado es la cadena ’52’, que no debe confundirse con el valor entero 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:

>>> motos = 52
>>> "En julio vendimos %d motos." % motos
’En julio vendimos 52 motos.’

La secuencia de formato ’%f’ formatea el siguiente elemento de la tupla como un número en coma flotante, y ’%s’ formatea el siguiente elemento como una cadena:

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

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:

>>> "%d %d %d" % (1,2)
TypeError: not enough arguments for format string
>>> "%d" % ’dólares’
TypeError: illegal argument type for built-in operation

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:

>>> "%6d" % 62
’    62’
>>> "%12f" % 6.1
’    6.100000’

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:

>>> "%-6d" % 62
’62    ’

También podemos especificar el número de decimales para los números en coma flotante:

>>> "%12.2f" % 6.1
’        6.10’

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.

Imagine, 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 un informe formateado:

def informe (tarifas) :
  estudiantes = tarifas.keys()
  estudiantes.sort()
  for estudiante in estudiantes:
    print "%-20s %12.02f" % (estudiante, tarifas [estudiante])

Para probar la función, crearemos un pequeño diccionario e imprimiremos el contenido:

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

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.

11.3. Directorios

Cuando usted crea un archivo nuevo abriéndolo y escribiendo, el nuevo archivo va al directorio en uso (aquél en el que etuviese al ejecutar el programa). Del mismo modo, cuando abre un archivo para leerlo, Python lo busca en el directorio en uso.

Si quiere abrir un archivo de cualquier otro sitio, tiene que especificar la ruta del archivo, que es el nombre del directorio (o carpeta) donde se encuentra éste:

>>> f = open("/usr/share/dict/words","r")
>>> print f.readline()

Aarhus

Este ejemplo abre un archivo llamado words que está en un directorio llamado diet, que está en share, que está en usr, que está en el directorio de nivel superior del sistema, llamado /.

No puede usar / como parte del nombre de un archivo; está reservado como delimitador entre nombres de archivo y directorios.

El archivo /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.

11.4. Encurtido

Para poner valores en un archivo, debe convertirlos en cadenas. Ya ha visto cómo hacerlo con str:

>>> f.write(str(12.3))
>>> f.write(str([1,2,3]))

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:

>>> f.readline()
’12.3[1, 2, 3]’

La solución es el encurtido, llamado así porque “conserva” estructuras de datos. El módulo pickle contiene las órdenes necesarias. Para usarlo, importe pickle y luego abra el archivo de la forma habitual:

>>> import pickle
>>> f = open("test.pck","w")

Para almacenar una estructura de datos, use el método dump y luego cierre el archivo de la forma habitual:

>>> pickle.dump(12.3,f)
>>> pickle.dump([1,2,3],f)
>>> f.close()

Ahora podemos abrir el archivo para leer y cargar las estructuras de datos que volcamos ahí:

>>> f = open("test.pck","r")
>>> x = pickle.load(f)
>>> x
12.3
>>> type(x)
<type ’float’>
>>> y = pickle.load(f)
>>> y
[1, 2, 3]
>>> type (y)
<type ’list’>

Cada vez que invocamos load obtenemos un valor del archivo, completo con su tipo original.

11.5. Excepciones

Siempre que ocurre un error en tiempo de ejecución, se crea una excepción. Normalmente el programa se para y Python presenta un mensaje de error.

Por ejemplo, la división por cero crea una excepción:

>>> print 55/0
ZeroDivisionError: integer division or modulo

Un elemento no existente en una lista hace lo mismo:

>>> a = []
>>> print a [5]
IndexError: list index out of range

O el acceso a una clave que no está en el diccionario:

>>> b = {}
>>> print b[’qué’]
KeyError: qué

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.

A veces queremos realizar una operación que podría provocar una excepción, pero no queremos que se pare el programa. Podemos manejar la excepción usando las sentencias try y 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.

nombreArch = raw_input(’Introduce un nombre de archivo: ‘)
try:
  f = open (nombreArch, "r")
except:
  print ’No hay ningún archivo que se llame’, nombreArch

La sentencia try ejecuta las sentencias del primer bloque. Si no se produce ninguna excepción, pasa por alto la sentencia except. Si ocurre cualquier excepción, ejecuta las sentencias de la rama except y después continúa.

Podemos encapsular esta capacidad en una función: existe acepta un nombre de archivo y devuelve verdadero si el archivo existe y falso si no:

def existe(nombreArch):
  try:
    f = open(nombreArch)
    f.close()
    return 1
  except:
    return 0

Puede usar múltiples bloques except para manejar diferentes tipos de excepciones. El Manual de Referencia de Python contiene los detalles.

Si su programa detecta una condición de error, puede hacer que lance (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.

def tomaNumero() :                  # Recuerde, los acentos están
  x = input(’Elige un número: ’)    # prohibidos en los nombres
  if x == 17 :                      # de funciones y variables!
    raise ’ErrorNúmeroMalo’, ’17 es un mal número’
  return x

La sentencia raise acepta dos argumentos: el tipo de excepción e información específica acerca del error. ErrorNúmeroMalo es un nuevo tipo de excepción que hemos inventado para esta aplicación.

Si la función llamada tomaNumero maneja el error, el programa puede continuar; en caso contrario, Python imprime el mensaje de error y sale:

>>> tomaNumero()
Elige un número: 17
ErrorNúmeroMalo: 17 es un mal número

El mensaje de error incluye el tipo de excepción y la información adicional que usted proporcionó.

Como ejercicio, escriba una función que use tomaNumero para leer un número del teclado y que maneje la excepción ErrorNumeroMalo.

11.6. Glosario

archivo: Una entidad con nombre, normalmente almacenada en un disco duro, disquete o CD-ROM, que contiene una secuencia de caracteres.

directorio: Una colección, con nombre, de archivos, también llamado carpeta.

ruta: Una secuencia de nombres de directorio que especifica la localización exacta de un archivo.

archivo de texto: Un archivo que contiene caracteres imprimibles organizados en líneas separadas por caracteres de salto de línea.

sentencia break: Una sentencia que provoca que el flujo de ejecución salga de un bucle.

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.

operador de formato: El operador % 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.

cadena de formato: Una cadena que contiene caracteres imprimibles y secuencias de formato que indican cómo formatear valores.

secuencia de formato: Una secuencia de caracteres que comienza con % e indica cómo formatear un valor.

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.

excepción: Un error que ocurre en tiempo de ejecución.

manejar: Impedir que una excepción detenga un programa utilizando las sentencias try y except.

lanzar: Señalar una excepción usando la sentencia raise.