6.2. Trabajo con objetos de fichero

Python incorpora una función, open, para abrir ficheros de un disco. open devuelve un objeto de fichero, que tiene métodos y atributos para obtener información sobre el fichero abierto y manipularlo.

Ejemplo 6.3. Apertura de un fichero

>>> f = open("/music/_singles/kairo.mp3", "rb") 1
>>> f                                           2
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.mode                                      3
'rb'
>>> f.name                                      4
'/music/_singles/kairo.mp3'
1 El método open puede tomar hasta tres parámetros: un nombre de fichero, un modo y un parámetro para búfer. Sólo el primero, el nombre, es obligatorio; los otros dos son opcionales. Si no lo especifica, el fichero se abrirá en modo lectura y texto. Aquí estamos abriendo el fichero en modo binario (print open.__doc__ muestra una gran explicación de todos los modos posibles).
2 La función open devuelve un objeto (a estas alturas, esto no debería sorprenderle). Un objeto de fichero tiene varios atributos útiles.
3 El atributo mode de un objeto de fichero nos dice en qué modo se abrió.
4 El atributo name de un objeto de fichero nos dice el nombre del fichero que el objeto ha abierto.

6.2.1. Lectura de un fichero

Tras abrir un fichero, lo más importante que querrá hacer es leerlo, como se muestra en el siguiente ejemplo.

Ejemplo 6.4. Lectura de un fichero

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.tell()              1
0
>>> f.seek(-128, 2)       2
>>> f.tell()              3
7542909
>>> tagData = f.read(128) 4
>>> tagData
'TAGKAIRO****THE BEST GOA         ***DJ MARY-JANE***            
Rave Mix                      2000http://mp3.com/DJMARYJANE     \037'
>>> f.tell()              5
7543037
1 Un objeto de fichero mantiene el estado del fichero que ha abierto. El método tell del objeto nos indica la posición actual dentro del fichero abierto. Dado que no hemos hecho nada con este fichero aún, la posición actual es 0, que es el comienzo del fichero.
2 El método seek de un objeto de fichero mueve la posición actual a otro lugar en el fichero abierto. El segundo parámetro especifica cómo interpretar el primero; 0 significa moverse en una posición absoluta (contando desde el comienzo del fichero), 1 significa moverse a una posición relativa (desde la actual), y 2 significa moverse a una opsición relativa al final del fichero. Dado que las etiquetas de MP3 que buscamos están almacenadas al final del fichero, usaremos 2 y le diremos al objeto que se mueva a la posición 128 antes del final del fichero.
3 El método tell confirma que la posición actual del fichero ha cambiado.
4 El método read lee un número especificado de bytes del fichero abierto y devuelve una cadena con los datos leídos. El parámetro opcional especifica el número máximo de bytes a leer. Si no se especifica parámetro, read leerá hasta el final del fichero (podría haber dicho simplemente read() aquí, ya que sabemos exactamente el sitio del fichero donde nos encontramos y estamos, en realidad, leyendo los últimos 128 bytes). Los datos leídos se asignan a la variable tagData, y se actualiza la posición actual en el fichero basándose en el número de bytes leídos.
5 El método tell confirma que la posición actual ha cambiado. Si hace los cálculos, comprobará que tras leer 128 bytes, la posición se ha incrementado en 128.

6.2.2. Cerrar ficheros

Los ficheros abiertos consumen recursos del sistema, y dependiendo del modo del fichero, puede que otros programas no puedan acceder a ellos. Es importante cerrar los ficheros tan pronto como haya terminado con ellos.

Ejemplo 6.5. Cierre de un fichero

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed       1
False
>>> f.close()      2
>>> f
<closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed       3
True
>>> f.seek(0)      4
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.tell()
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.read()
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.close()      5
1 El atributo closed de un objeto de fichero indica si el objeto tiene abierto un fichero o no. En este caso, el fichero sigue abierto (closed es False).
2 Para cerrar un fichero, llame al método close del objeto. Esto libera el bloqueo (si lo hubiera) que estaba manteniendo sobre el fichero, activa la escritura de búfers (si los hubiera) que el sistema aún no haya escrito realmente, y libera los recursos del sistema.
3 El atributo closed confirma que el fichero está cerrado.
4 Sólo porque un fichero esté cerrado no significa que el objeto deje de existir. La variable f continuará existiendo hasta que salga de ámbito o sea eliminada manualmente. Sin embargo, ninguno de los métodos que manipula ficheros abiertos funcionará una vez cerrado el fichero; todos lanzarán una excepción.
5 Invocar close en un objeto de fichero cuyo fichero ya está cerrado no lanza una excepción; falla sin advertirlo.

6.2.3. Gestión de errores de E/S

Ahora ya ha visto bastante para comprender el código de gestión de ficheros en el ejemplo fileinfo.py del capítulo anterior. Este ejemplo le muestra cómo abrir y leer un fichero de forma segura y lidiar con los errores elegantemente.

Ejemplo 6.6. Objetos de fichero en MP3FileInfo

        try:                                1
            fsock = open(filename, "rb", 0) 2
            try:                           
                fsock.seek(-128, 2)         3
                tagdata = fsock.read(128)   4
            finally:                        5
                fsock.close()              
            .
            .
            .
        except IOError:                     6
            pass                           
1 Como abrir y leer ficheros tiene su riesgo y puede provocar una excepción, todo el código está encapsulado en un bloque try...except. (¡Eh!, ¿no es maravilloso el sangrado estándarizado? Aquí es donde empezará a apreciarlo).
2 La función open puede lanzar una IOError (puede que el fichero no exista).
3 El método seek puede lanzar una IOError (puede que el fichero sea más pequeño que 128 bytes).
4 El método read puede lanzar una IOError (puede que el disco tenga sectores defectuosos, o esté en una unidad de red y ésta acabe de caerse).
5 Esto es nuevo: un bloque try...finally. Una vez la función open consiga abrir el fichero con éxito, querrá estar absolutamente seguro de cerrarlo, incluso si los métodos seek o read lanzan una excepción. Para esto es el bloque try...finally: el código en el bloque finally se ejecutará siempre, incluso si algo en el bloque try lanza una excepción. Piense en ello como código que se ha de ejecutar al salir, independientemente de lo que haya sucedido en medio.
6 Por fin, gestionamos la excepción IOError. Podría ser la IOError lanzada por la llamada a open, seek o read. En este caso no nos importa, porque todo lo que vamos a hacer es ignorarlo en silencio y continuar (recuerde, pass es una sentencia de Python que no hace nada). Eso es perfectamente válido; “gestionar” una excepción puede querer decir no hacer nada, pero explícitamente. Sigue contando como gestión, y el proceso continuará de forma normal en la siguiente línea de código tras el bloque try...except.

6.2.4. Escribir en ficheros

Como cabría esperar, también podemos escribir en ficheros de la misma manera que podemos leer de ellos. Hay dos modos básicos de escritura:

  • El modo "append" añadirá datos al final del fichero.
  • el modo "write" sobrescribirá el contenido del fichero.

Cualquiera de los modos creará el fichero automáticamente si no existía ya, de manera que no se necesita ningún tipo de molesta lógica "si el fichero de registro no existe aún, crea un fichero nuevo vacío de manera que puedas abrirlo por primera vez".

Ejemplo 6.7. Escribir en ficheros

>>> logfile = open('test.log', 'w')   1
>>> logfile.write('prueba con éxito') 2
>>> logfile.close()
>>> print file('test.log').read()     3
prueba con éxito
>>> logfile = open('test.log', 'a')   4
>>> logfile.write('línea 2')
>>> logfile.close()
>>> print file('test.log').read()     5
prueba con éxitolínea 2
1 Empezamos sin compasión, creando un nuevo fichero test.log o sobrescribiendo el que ya existía, y abriéndolo para escribir (el segundo parámetro "w" significa abrir el fichero para escribir). Sí, esto es tan peligroso como suena. Espero que no le importase el contenido previo del fichero, porque ya ha desaparecido.
2 Puede añadir datos al fichero recién abierto con el método write del objeto de fichero devuelto por open.
3 file es un sinónimo de open. Este one-liner abre el fichero, lee su contenido y lo imprime.
4 Usted sabe que test.log existe (ya que acaba de escribir en él), de manera que podemos abrirlo y añadir datos al final (el parámetro "a" implica abrir el fichero para adición). En realidad podría hacer esto incluso si el fichero no existiese, porque abrir el fichero para añadir lo creará en caso necesario. Pero este modo nunca dañará el contenido ya existente en el fichero.
5 Como puede ver, tanto la línea original que escribió como la segunda que acaba de añadir están en el fichero test.log. Observe también que no se incluye el retorno de carro. Como no lo ha escrito explícitamente ninguna de las veces, el fichero no lo incluye. Puede escribir un retorno de carro con el carácter "\n". Como no lo ha hecho, todo lo que se escribió en el fichero ha acabado pegado en la misma línea.

Lecturas complementarias sobre manipulación de ficheros