cat en Mac OS X

Ficheros de texto en Python

Cuando se trabaja con un programa, lo más normal del mundo es que queramos guardar el estado del mismo entre dos sesiones de trabajo. Es decir, cuando escribimos un artículo o un documento, no lo escribimos del tirón para luego imprimirlo “y se acabó”, sino que lo guardamos en el disco duro para, en otro momento, continuar trabajando con él. O, simplemente, para enviarlo por correo electrónico, archivarlo…

El trabajo con ficheros es una de las técnicas fundamentales que necesitamos dominar para escribir programas útiles con cualquier lenguaje de programación. En este artículo de PItando os voy a transmitir con ejemplos la forma de hacerlo con Python 3, como siempre, a través de ejemplos interactivos que podemos ir haciendo para navegar por las explicaciones. En el apartado de ejercicios os plantearé programas ejecutables para el siguiente artículo de la serie.

Para este artículo, además, me centraré fundamentalmente en ficheros de texto plano dejando los formatos binarios para otra entrada. El objetivo es proporcionaros una referencia rápida dentro de PItando a la hora de tener que trabajar con ficheros (guardar, leer información o configuración) durante nuestros proyectos.

Introducción a los ficheros. Modos de apertura y cierre del fichero

Un fichero es, para Python, un objeto que se crea con una operación de apertura, y sobre el que se pueden ejecutar funciones de lectura, escritura y cierre. Refleja un fichero en el disco duro, y dependiendo del modo de apertura, podremos hacer unas cosas u otras, incluyendo la pérdida total de la información del mismo 😉 Por eso, es muy importante comprender bien las consideraciones que vienen a continuación, y después con los ejemplos.

Para poder trabajar a lo largo del artículo vamos a crear un fichero de texto manualmente, con el editor de texto del sistema operativo con el que estés trabajando. En mi caso lo voy a crear con nano porque estoy trabajando en Linux (valdría igual para el Mac OS X); si estás trabajando en Windows puedes utilizar el Bloc de Notas.

Escribiremos el siguiente texto:

Ésta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano

Guárdalo con el nombre que tú quieras, en el directorio de tu elección. Yo lo guardaré como ejemplo.txt en ~/pitando , es decir, en el subdirectorio pitando dentro de mi directorio de usuario en Linux. En Windows lo podrías guardar, por ejemplo, en una carpeta llamada pitando dentro de tu directorio de instalación de Python (en mi caso, c:\prog\Python34, con lo que el directorio quedaría c:\prog\Python34\pitando).

Ahora abre IDLE (en la Raspberry Pi, es el icono Python 3 del menú de Programación), y sitúate en el directorio donde has guardado el fichero con las siguientes sentencias, escribiéndolas o copiándolas sobre IDLE, de una en una (no todas a la vez):

import os 
os.chdir('pitando')

El código de arriba funciona si estás en Mac o Linux (incluyendo la Raspberry Pi) y has creado el directorio pitando dentro de tu directorio personal, como decíamos antes. Si estás en Windows:

import os
os.chdir('c:\prog\Python34\pitando')

Una vez hecho esto, y si hemos grabado el fichero anterior con el nombre ejemplo.txt, prueba las siguientes sentencias (recuerda: una línea de cada vez):

fichero = open ('ejemplo.txt')
fichero.read()

El efecto que verás es que Python ha leído el fichero entero, sin ningún tipo de pudor, y lo ha transcrito en la pantalla traduciendo los retornos de carro por combinaciones '\n'.

Lectura completa de un fichero
Lectura completa de un fichero

Prueba a repetir la última de las sentencias, fichero.read(): lo que verás ahora es que no sale nada. ¿Qué ha ocurrido aquí?, ¿he perdido la información del fichero? No, lo que ocurre es que ya se ha avanzado hasta el final del fichero y no queda información por leer: el fichero se ha abierto de forma secuencial, se ha leído desde el principio hasta el final, y por esta vez no se puede hacer más.

Cierra el fichero con la sentencia de Python fichero.close() en IDLE, y comprueba si quieres en la consola que tu información sigue estando donde esperas, usando la orden cat en Linux o Mac OS X, o type en Windows.

cat en Mac OS X
cat en Mac OS X

Por supuesto, esto no es todo. Podrás haberte preguntado cosas como “¿Puedo leerlo poco a poco?, es decir, ¿qué hubiera ocurrido si el fichero ocupase un tamaño demasiado grande?, ¿puedo rebobinar el fichero de alguna forma, para volverlo a leer?, ¿qué pasa si quiero escribir?“. Vamos a tratar de responderlas todas, al menos a un nivel suficiente para que empieces a trabajar por tu cuenta.

Modos de apertura de ficheros

Lo primero que debes conocer es el modo de apertura del fichero, y escoger el que más te convenga. Este modo es el segundo parámetro de la función que ya vimos, y su modo por defecto, si no se especifica nada, es en modo lectura de texto. Siguiendo con el ejemplo, en el que abríamos el fichero con la sentencia fichero = open ('ejemplo.txt'), podríamos haber escrito fichero = open ('ejemplo.txt', 'r') o fichero = open ('ejemplo.txt', 'rt') para obtener exactamente los mismos resultados (read, o read text sería la traducción de los modos 'r' y 'rt' , respectivamente).

Puedes probarlo: el siguiente código tendrá exactamente el mismo efecto que el del ejemplo anterior (usando IDLE, copia una línea de cada vez):

fichero = open ('ejemplo.txt', 'rt')
fichero.read()
fichero.close()

En esta tabla que viene a continuación tienes todos los modos posibles que nos pueden interesar por el momento, con comentarios sobre su efecto.

Modo de apertura Efecto
'r', 'rt' Lectura en modo texto, en la codificación por defecto. Abre el fichero en la posición inicial para leerlo entero. Éste es el modo por defecto, es decir, si no se especifica ningún modo se abrirá en modo lectura y para texto.
'rb' Lectura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Abre el fichero en la posición inicial para leerlo entero.
'r+' Lectura y escritura en modo texto, en la codificación por defecto. Abre el fichero en la posición inicial para leerlo entero, o para actualizarlo.
'w', 'wt' Escritura en modo texto, en la codificación por defecto. Crea un fichero vacío, borrando su contenido anterior si ya existía.
'wb' Escritura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Crea un fichero vacío, borrando su contenido anterior si ya existía.
'x', 'xt' Modo de creación exclusiva en modo texto, en la codificación por defecto. Crea un fichero vacío, y si existe previamente emitirá un error (y no borrará el fichero previamente existente).
'xb' Modo de creación exclusiva en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Crea un fichero vacío, y si existe previamente emitirá un error (y no borrará el fichero previamente existente).
'a', 'at' Escritura en modo texto, en la codificación por defecto. Abre el fichero en la última posición, para añadirle contenido.
'ab' Escritura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Abre el fichero en la última posición, para añadirle contenido.

Si quisiésemos crear un fichero en el disco duro, debemos abrirlo en modo de escritura, pero con mucho cuidado. Como hemos visto en la tabla, los modos 'w' y 'wb' crean un fichero vacío, y si existía un fichero con el mismo nombre, lo borrará en el proceso. Por eso, a no ser que nuestro fichero sea de trabajo o queramos borrar cualquier versión anterior, es mucho más seguro en general usar el modo 'x' en vez de 'w', ya que su funcionamiento es tal que si ya existía emitirá un error. De hecho, si escribes en este punto la sentencia fichero = open ('ejemplo.txt', 'x'), el sistema te devolverá el siguiente error, que indica precisamente que el fichero existe:

Traceback (most recent call last):
  File "", line 1, in 
    fichero = open ('ejemplo.txt', 'x')
FileExistsError: [Errno 17] File exists: 'ejemplo.txt

En la siguiente sección, que ya trata sobre operar con el contenido de un fichero a través de los distintos modos, probaremos también a destruir ficheros usando los modos derivados de  'w'.

Vamos a trabajar en cierto detalle con estos modos, pero teniendo en cuenta que en este artículo no experimentaremos con formatos binarios (quedan para otro día), centrándonos exclusivamente en ficheros de texto.

Cierre del fichero

Es extremadamente importante cerrar siempre un fichero después de trabajar con él con la sentencia que ya hemos experimentado:

fichero.close()

Si hemos estado leyendo un fichero, cerrarlo libera memoria del ordenador; si estábamos escribiendo en él, cerrar el fichero vuelca (o consolida) todos los cambios en el disco duro y libera la memoria que Python tenía reservada para el trabajo con él.

¿Qué es eso de volcar todos los cambios en el disco duro? ¿Acaso los ficheros no están ya en el disco duro? No mientras trabajas con ellos. Todos los programas informáticos que trabajan con fichero lo hacen en memoria: de forma autónoma (sin hacer tú nada) copian una sección pequeña del fichero, bien definida, a la memoria RAM. Después trabajan con ella, e igualmente de forma autónoma (transparente para ti) la escriben en el disco duro cuando la edición progresa a otras áreas del fichero. Cerrar el fichero fuerza a que todos los cambios pendientes de escribir al disco se escriban, acción que en inglés (y por lo tanto en muchos lenguajes) recibe el nombre de flush.

Trabajando con el contenido de un fichero

En este apartado vamos a ver todas las formas que tenemos de abrir, leer y escribir un fichero, usando cadenas de texto normales.

Distintas formas de leer el contenido de un fichero

Ya hemos visto que mediante la sentencia fichero.read() leemos todo el contenido del fichero, sea lo largo que sea. Existen otros métodos para leer un fichero de texto, que son los siguientes:

  • Lectura de un número preciso de caracteres: se usa para controlar la memoria que usa nuestro programa a la hora de leer un fichero, y se obtiene pasando como argumento a la función read  el número de bytes a leer.
    • Por ejemplo, si quisiésemos leer un fichero de 8 kB en 8 kB, usaríamos la sentencia fichero.read(8192) .
    • La función devuelve una cadena cuyo tamaño en bytes es igual o menor al valor del argumento. Si llega al final del fichero en una ejecución, puede devolver menos.
    • Si se estuviera en el final del fichero, al ejecutarla devolvería una cadena vacía, es decir, ''. Esto es lo que ocurrió en el ejemplo del apartado anterior.
    • El número de caracteres devuelto depende de la codificación de los mismos. Si la codificación es ASCII, coincide con el número de bytes, pero si es UTF de 16 bit, será la mitad.
  • Lectura de una línea: se trata de la función fichero.readline()  y se usa para obtener líneas, es decir, cadenas de texto separadas por retornos de carro (caracter '\n' ).
    • Por cada línea incluirá su retorno de carro.

El tipo devuelto por todas las funciones de lectura es str  siempre que el modo de apertura es de texto.

Vamos a ver unos ejemplos. Nuestro fichero de ejemplo, ejemplo.txt, tiene exactamente 64 bytes si se consulta el tamaño según el sistema operativo. Veamos el comportamiento de la función de lectura por bytes cuando le pasamos como argumento 45. Escribe lo siguiente en IDLE:

fichero = open ('ejemplo.txt')

bloque_1 = fichero.read(45);
bloque_2 = fichero.read(45);
bloque_3 = fichero.read(45);

bloque_1
len(bloque_1)

bloque_2
len(bloque_2)

bloque_3
len(bloque_3)

 Verás lo siguiente, en Windows:

Funcionamiento de la lectura por bloques en Python
Funcionamiento de la lectura por bloques en Python

En Windows la codificación por defecto para los ficheros de texto es tal que cada caracter ocupa 1 byte excepto algunos no visibles, como por ejemplo el retorno de carro. Si te fijas, la suma de todos los caracteres es 59, y no 64 como dice Windows. Python representa el caracter retorno de carro como '\n', y lo almacena en un único caracter, pero Windows realmente usa dos caracteres: retorno de carro (CR) y nueva línea (LF), de un byte cada uno. De ahí la diferencia de 5 caracteres en 5 líneas de texto. En Mac OS X ocurre exactamente lo mismo. En general, en todas las plataformas ocurrirá lo mismo si la codificación de caracteres del sistema es de 8 bits.

Por último, fíjate que el tercer bloque es una cadena vacía y contiene 0 caracteres. Es importante darse cuenta de esto porque esos dos hechos nos dan una condición de salida para un bucle (es decir, cosas que comprobar para salir de un bucle) para el caso en  que quisiéramos escribir un programa que procesase un fichero por bloques de caracteres.

Veamos ahora la lectura de líneas, tanto separadamente como en bucle: cierra para ello el fichero con fichero.close()  para después transcribir el siguiente código:

fichero = open ('ejemplo.txt')

fichero.readline();
fichero.readline();

for linea in fichero:
    print(linea, ' ** longitud:', len(linea));

 El resultado debería ser éste:

Lectura de ficheros por líneas
Lectura de ficheros por líneas

Las dos primeras líneas no deberían sorprendernos, pero la sentencia for  merece atención:

  • Los ficheros se ofrecen al programa, en parte, como listas de líneas, de tal forma que pueden recorrerse usando un bucle como en el programa, y no hay que preocuparse de cuándo se alcanza el final de fichero.
    • “en parte” ya que si escribimos fichero  en IDLE no se imprime la lista de las líneas del fichero, sino una cadena que describe al fichero: un descriptor.
    • Si queremos obtener todas las lineas del fichero como una lista, podemos ejecutar fichero.readlines() o list(fichero)
Representación de un descriptor de un fichero.
Representación de un descriptor de un fichero.
  • Como era de esperar, cuando la función print  se encuentra un retorno de carro, lo imprime. Es decir, cuando simplemente evaluamos una variable, IDLE imprime los retornos de carro como su representación '\n' , pero si una cadena se imprime mediante la función print , el retorno de carro se imprime como lo que es.

Movernos por el contenido de un fichero: seek y tell en ficheros de texto

Las funciones seek y tell permiten movernos por los contenidos de un fichero de texto, hasta cierto punto. Digo hasta cierto punto, porque los ficheros de texto tienen serias limitaciones con la función seek. Veamos cuáles por qué.

A priori, seek admite dos parámetros: el desplazamiento y el punto de partida.

fichero.seek(desplazamiento, desdeDonde)

Ambos parámetros son variables numéricas, y el comportamiento es de la siguiente forma:

  • desdeDonde es opcional y puede tomar los siguientes valores:
    • 0 comienza el desplazamiento desde el inicio del fichero. Este es el valor por defecto, es decir, el valor si no se especifica este parámetro.
    • 1 comienza el desplazamiento desde la posición actual.
    • 2 comienza el desplazamiento desde el final del fichero.
  • desplazamiento puede ser un entero positivo o negativo, en este último caso sólo si desdeDonde es 1 ó 2.

Sin embargo, los ficheros de texto tienen un comportamiento especial, no demasiado bien documentada en python.org, y la función seek sólo permite realizar desplazamientos relativos al inicio del fichero. Además, los valores del desplazamiento sólo sirven si son los que devuelve la otra función que trataré en este apartado, que es tell

tell es una función que devuelve la posición de lectura o escritura en un fichero, en bytes si el fichero se abre en modo binario, y de forma opaca si el fichero se abre en modo texto. Es decir, la posición de lectura o escritura en un fichero de texto es un número cuya forma de calcularse o determinarse está deliberadamente oculta, y debe tratarse como un valor sin significado pero que, de alguna forma, funciona. Representa una posición, una dirección dentro de un espacio de posiciones, que no tiene que corresponderse con lo que nosotros entenderíamos por una secuencia de caracteres (el primero, el segundo,…).

Por este especial funcionamiento de los ficheros de texto en Python (cuyos detalles nos están ocultos), la función seek  tendrá un comportamiento arbitrario si intentamos operar con un desplazamiento que no haya sido generado por la función tell. Esto implica una serie de complicaciones a la hora de tratar un fichero que exploraremos en a través de los ejercicios.

Al final del artículo usaré estas dos funciones cuando tratemos de actualizar un fichero con el modo 'r+'.

Escritura de ficheros

Para escribir un fichero de texto tenemos los modos 'a', 'x', 'w' y 'r+'. El último de ellos permite leer y escribir al mismo tiempo para actualizar un fichero. Vamos a verlos precisamente por ese orden para comprender su efecto, una vez entendamos el uso de la función write , que es la encargada de las operaciones de escritura sobre el contenido.

write acepta como parámetro la cadena a escribir. Es una función estricta en el sentido de que cualquier cosa que queramos escribir debe ser convertida primero a una cadena, mediante la función str . Por ejemplo, fichero.write('Otra línea más\n')  es una función de escritura válida, pero fichero.write(42)  no lo es; deberíamos escribir fichero.write(str(42)). Como es de esperar, write  no añade el caracter de nueva línea, por lo que hay que tenerlo en cuenta y añadirlo si lo que queremos es añadir una nueva línea a un fichero. Por último, mencionar que devuelve el número de caracteres escritos.

Prueba los siguientes ejemplos.

Añadir líneas a un fichero

La apertura del fichero en modo “ampliación” se realiza pasando como parámetro de modo el 'a'. El siguiente código,

fichero = open ( 'ejemplo.txt', 'a' ) 
fichero.write('Línea añadida como ejemplo.\n') 
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 28. Si examinamos el contenido del fichero veremos lo siguiente:

Ésta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano
Línea añadida como ejemplo.

Creación exclusiva de un fichero

Como ya vimos, abrir un fichero en modo 'x', provoca un error si éste existe. Por lo demás, funciona exactamente igual que una escritura como la que hemos visto pero… si el fichero estuviese vacío. El siguiente código,

fichero = open ( 'ejemplo_2.txt', 'x' ) 
fichero.write('Línea añadida como ejemplo.\n') 
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 28. Si examinamos el contenido del fichero ejemplo_2.txt veremos lo siguiente:

Línea añadida como ejemplo.

Sin embargo, si escribimos fichero = open ( 'ejemplo.txt', 'x' ) obtendremos el ya conocido error, que indica precisamente que el fichero existe:

Traceback (most recent call last):
  File "", line 1, in 
    fichero = open ('ejemplo.txt', 'x')
FileExistsError: [Errno 17] File exists: 'ejemplo.txt

Creación de un fichero existente destruyendo su contenido previo

Como ya os he avisado, abrir un fichero en modo 'w', provoca la destrucción de su contenido anterior si éste existe. El siguiente código,

fichero = open ( 'ejemplo_2.txt', 'w' ) 
fichero.write('Nuevo contenido')
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 15. Si examinamos el contenido del fichero ejemplo_2.txt veremos lo siguiente:

Nuevo contenido

en lugar del contenido del apartado anterior: Línea añadida como ejemplo. ya no existe.

Actualización de un fichero

Abrir un fichero con el modo 'r+'  permite leerlo y escribir valores. Vamos a ver cómo funciona con un poco más de detalle. Ejecuta las siguientes sentencias, una a una, en IDLE:

fichero = open ('ejemplo.txt', 'r+')
fichero.write('Prueba')
fichero.seek(0)
fichero.read()
fichero.seek(0)
fichero.write('Esta e')
fichero.read()
fichero.write('CONTENIDO ADICIONAL')
fichero.seek(0)
fichero.read()
fichero.close()

Estas sentencias que has escrito demuestran el comportamiento del modo de actualización de un fichero, y permite sacar como conclusiones que:

  1. Lo que hacemos al escribir en este modo es sobreescribir, y no insertar, la información existente.
  2. Además, deja la posición de lectura o escritura tras el último caracter escrito.
  3. Por último, si estamos en el final del fichero, además, funciona como el modo anteriormente comentado 'a' 
Modo de actualización de ficheros
Modo de actualización de ficheros

El contenido del fichero resultante es el siguiente:

Esta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano
Línea añadida como ejemplo.
CONTENIDO ADICIONAL

Voy a plantearos a continuación una serie de ejercicios, tras el resumen de las consideraciones más importantes.

Consideraciones

  1. Escoge muy cuidadosamente el modo de apertura de los ficheros en tu programa: de ello puede depender que tus usuarios (los usuarios de tu programa) no pierdan información. Los ordenadores son buenos siguiendo instrucciones, no leyendo las mentes (esa frase abría mi proyecto de fin de carrera hace casi 11 años, y es de Donald Erving Knuth).
  2. Cierra siempre el fichero: sé económico con los recursos que usa tu programa. Siempre que termines de leer o escribir un fichero, ciérralo: es una forma de devolver memoria al sistema operativo para poderla usar más adelante.
  3. A la hora de leer un fichero de una línea  en una línea, el ejemplo que hace uso del bucle for es el mejor: muy limpio en lo que a código se refiere, y muy eficiente en uso de recursos.
  4. Leer todo un fichero de golpe (fichero.read() ) o leer todas las líneas de golpe para tener una lista de líneas (fichero.readlines() ) puede dejar tu ordenador sin memoria. Evítalo siempre que puedas, o, si lo usas, intenta que siempre sea por una buena razón y sólo en casos en los que tú y sólo tú seas quien define el tamaño máximo de los ficheros.

Ejercicios

  1. Crea un programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) como parámetro, y que devuelva el número de caracteres total, y el número total de palabras.
    1. Sólo puedes abrir el fichero una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 
  2. Crea otro programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) y que produzca otro, de igual nombre pero con extensión .pitando, con el contenido del primero fichero pero en mayúsculas.
    1. Sólo puedes abrir el fichero original una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 

Recuerda que puedes repasar conceptos como las rutas relativas al directorio actual y cómo hacer programas ejecutables por línea de comandos en los propios artículos de PItando.

Recuerda, también, que puedes dejarme comentarios o dudas en los comentarios de este artículo, o a través del formulario de contacto o de la dirección de correo que allí se encuentra.

2 comentarios en “Ficheros de texto en Python

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *