Saltar a contenido

Ficheros

Chain (1)

  1. Maksym Kaharlytskyi Unsplash

Aunque los ficheros encajarían más en un apartado de «entrada/salida» ya que representan un medio de almacenamiento persistente, también podrían ser vistos como estructuras de datos, puesto que nos permiten guardar la información y asignarles un cierto formato.

Un fichero es un conjunto de bytes almacenados en algún dispositivo. El sistema de ficheros es la estructura lógica que alberga los ficheros y está jerarquizado a través de directorios (o carpetas). Cada fichero se identifica unívocamente a través de una ruta que nos permite acceder a él.

En esta sección nos centraremos en el manejo de ficheros de texto plano (aquellos que son entendibles por humanos). Pero también existen ficheros binarios que Python es capaz de manejar.

Hay tres modos de apertura de un fichero:

  • Lectura.
  • Escritura.
  • Añadido.

Lectura de un fichero

Para abrir un fichero en modo lectura utilizamos la función open() con el modificador 'r'.

En el siguiente ejemplo vamos a leer el contenido de un fichero que contiene las temperaturas mínimas y máximas de cada día de la última semana en una región determinada:

store/temps.dat
1
2
3
4
5
6
7
23 29
23 31
26 34
23 33
22 29
22 28
22 28

Abrir un fichero significa crear un objeto «manejador» que nos permita realizar operaciones sobre dicho fichero:

>>> f = open('store/temps.dat', 'r')#(1)!

  1. Es posible omitir 'r' ya que el modo de apertura por defecto es lectura.

La función open() recibe como primer argumento la ruta al fichero que queremos manejar (como un «string») y como segundo argumento el modo de apertura (también como un «string»). Nos devuelve el manejador del fichero, que en este caso lo estamos asignando a una variable llamada f pero le podríamos haber puesto cualquier otro nombre.

Rutas relativas vs Rutas absolutas

Es importante dominar los conceptos de ruta relativa y ruta absoluta para el trabajo con ficheros (véase este artículo de Sánchez Corbalán).

El manejador del fichero se implementa mediante un flujo de entrada/salida para las operaciones de lectura/escritura. Este objeto almacena, entre otras cosas, la ruta al fichero, el modo de apertura y la codificación:

>>> f
<_io.TextIOWrapper name='store/temps.dat' mode='r' encoding='UTF-8'>

Codificaciones

Existen muchas codificaciones de caracteres para ficheros, pero la más utilizada es UTF-8 ya que es capaz de representar cualquier caracter Unicode al utilizar una longitud variable de 1 a 4 bytes.

Hay que tener en cuenta que la ruta al fichero que abrimos (en modo lectura) debe existir, ya que de lo contrario obtendremos un error:

>>> f = open('foo.txt', 'r')
Traceback (most recent call last):
  Cell In[1], line 1
    f = open('foo.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'foo.txt'

Una vez abierto el fichero ya podemos proceder a leer su contenido. Para ello Python nos ofrece la posibilidad de leer todo el fichero de una vez o bien leerlo línea a línea.

Lectura completa

Siguiendo nuestro ejemplo de temperatura, veamos cómo leer todo el contenido del fichero de una sola vez. Para esta operación, Pythos nos provee de dos funciones:

Devuelve todo el contenido del fichero como una única cadena de texto:

>>> f = open('store/temps.dat')

>>> f.read()
'23 29\n23 31\n26 34\n23 33\n22 29\n22 28\n22 28\n'

Devuelve todo el contenido del fichero como una lista donde cada elemento de la lista representa una línea del fichero:

>>> f = open('store/temps.dat')

>>> f.readlines()
['23 29\n', '23 31\n', '26 34\n', '23 33\n', '22 29\n', '22 28\n', '22 28\n']

Saltos de línea

Nótese que, en ambos casos, los saltos de línea '\n' siguen apareciendo en los datos leídos, por lo que habría que limpiar estos caracteres.

Lectura línea a línea

Hay situaciones en las que interesa leer el contenido del fichero línea a línea. Imaginemos un fichero de tamaño considerable (varios GB). Si intentamos leer completamente este fichero de sola una vez podríamos ocupar demasiada RAM y reducir el rendimiento de nuestra máquina.

Es por ello que Python nos ofrece varias aproximaciones a la lectura de ficheros línea a línea. La más usada es iterar sobre el propio manejador del fichero, ya que los ficheros son estructuras de datos iterables.

Veamos cómo aplicarlo en el ejemplo de las temperaturas:

>>> f = open('store/temps.dat')

>>> for line in f:    # that easy!
...     print(line)
...
23 29

23 31

26 34

23 33

22 29

22 28

22 28

Enumerando líneas

En ocasiones no sólo necesitamos recorrer cada línea del fichero sino también ir llevando un «índice» que nos indique el número de línea que estamos procesando.

Dado que los manejadores de ficheros también son objetos iterables podemos hacer uso de la función enumerate().

A continuación mostramos un ejemplo donde usamos esta característica para incluir los días de la semana en el fichero de temperaturas:

>>> f = open('store/temps.dat')

>>> for line_no, line in enumerate(f, start=1):
...     print(f'D{line_no}: {line.strip()}')
...
D1: 23 29
D2: 23 31
D3: 26 34
D4: 23 33
D5: 22 29
D6: 22 28
D7: 22 28

Lectura de una línea

Es posible que sólo necesitemos leer una línea del fichero. Para ello Python ofrece la función readline() que nos devuelve la «siguiente» línea del fichero.

Veamos cómo hacerlo con el ejemplo del fichero de temperaturas:

>>> f = open('store/temps.dat')

>>> f.readline()
'23 29\n'

Es importante señalar que cuando utilizamos la función readline() el puntero de lectura se deplaza a la siguiente línea del fichero. Este hecho nos permite seguir leyendo por donde íbamos.

A continuación se muestra un trozo de código sobre el ejemplo de las temperaturas en el que mezclamos ambas técnicas de lectura:

>>> f = open('store/temps.dat')#(1)!

>>> for _ in range(3):#(2)!
...     print(f.readline().strip())
...
23 29
23 31
26 34

>>> for line in f:#(3)!
...     print(line.strip())
...
23 33
22 29
22 28
22 28

  1. Puntero de lectura en la posición 0 del fichero.
  2. Lectura de las 3 primeras líneas del fichero.
  3. Lectura de las restantes líneas del fichero.

La función readline() devuelve la cadena vacía cuando el puntero de lectura ha llegado al final del fichero. Bajo esta premisa podríamos implementar una forma poco ortodoxa de leer un fichero:

>>> f = open('store/temps.dat')

>>> while line := f.readline():
...     print(line.strip())
...
23 29
23 31
26 34
23 33
22 29
22 28
22 28

Lectura con posicionamiento

Hay que tener en cuenta que, una vez que leemos un fichero, no lo podemos volver a leer «directamente». O dicho de otra manera, el iterable que lleva implícito «se agota».

Analicemos este escenario con el ejemplo anterior:

>>> f = open('store/temps.dat')

>>> for line in f:#(1)!
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28

>>> for line in f:#(2)!
...     print(line.strip(), end=' ')

  1. Recorrido de todo el fichero línea a línea.
    • Al intentar recorre de nuevo el fichero no sale nada por pantalla.
    • El puntero de lectura ha llegado al final.

Aunque no es tan usual, existe la posibilidad de volver a leer el fichero desde el principio reposicionando el puntero de lectura mediante la función seek().

A continuación se muestra como ejemplo una «doble» lectura del fichero de temperaturas:

>>> f = open('store/temps.dat')

>>> for line in f:
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28

>>> f.seek(0)#(1)!
0

>>> for line in f:
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28

    • Situamos el puntero de lectura en el byte 0.
    • Devuelve la posición absoluta (en bytes) en la que se encuentra el puntero de lectura.
Posicionamiento relativo

La función seek() también permite indicar un desplazamiento relativo si se usa el segundo argumento.

Uso Mover el puntero de lectura...
f.seek(10, 0) 10 bytes desde el principio del fichero (0) DEFAULT
f.seek(7, 1) 7 bytes desde la posición actual del puntero de lectura (1)
f.seek(-5, 2) 5 bytes desde el final del fichero (2)

Escritura de un fichero

Para abrir un fichero en modo escritura utilizamos la función open() con el modificador 'w'.

A continuación vamos a implementar un ejemplo en el que queremos escribir en un fichero las temperaturas mínimas y máximas de la última semana en una región determinada.

Lo primero será abrir el fichero en modo escritura:

>>> f = open('store/temps.dat', 'w')

La apertura de un fichero en modo escritura borrará todo el contenido que contuviera.

Ruta al fichero

Las carpetas (o directorios) intermedios hasta llegar al fichero indicado deben existir previamente. De lo contrario obtendremos un error:

>>> f = open('foo/bar/temps.dat', 'w')
Traceback (most recent call last):
  Cell In[1], line 1
    f = open('foo/bar/temps.dat', 'w')
    return io_open(file, *args, **kwargs)
FileNotFoundError: [Errno 2] No such file or directory: 'foo/bar/temps.dat'

Ahora supongamos que disponemos de una estructura de datos (tupla de tuplas) con las temperaturas:

>>> temps = (
...   (23, 29),
...   (23, 31),
...   (26, 34),
...   (23, 33),
...   (22, 29),
...   (22, 28),
...   (22, 28),
... )

Python proporciona la función write() para escribir en un fichero:

>>> for min_temp, max_temp in temps:
...     f.write(f'{min_temp} {max_temp}\n')#(1)!
...
>>> f.close()#(2)!

    • Construimos un «f-string» con la cadena a escribir en el fichero.
    • No olvidarse del saltó de línea '\n' para incluir cada línea.
  1. Es fundamental cerrar el fichero, especialmente en modo escritura, ya que de lo contrario podríamos perder los datos.

Si tratáramos de escribir directamente las temperaturas (como enteros) en el fichero, obtendríamos un error:

>>> for min_temp, max_temp in temps:
...     f.write(min_temp)#(1)!
...     f.write(max_temp)#(2)!
...
Traceback (most recent call last):
  Cell In[1], line 2
    f.write(min_temp)
TypeError: write() argument must be str, not int

  1. La función write() sólo admite cadenas de texto.
  2. La función write() sólo admite cadenas de texto.

Usando contextos

Python proporciona gestores de contexto como aproximación al manejo de ficheros. En este escenario usaremos la sentencia with y el contexto creado se ocupará de abrir y cerrar el fichero automáticamente (incluso si ha habido cualquier error).

Veamos cómo aplicarlo con el ejemplo anterior de las temperaturas:

>>> with open('store/temps.dat', 'w') as f:#(1)!
...     for min_temp, max_temp in temps:
...         f.write(f'{min_temp} {max_temp}\n')
...

    • Abrimos el fichero en modo escritura.
    • Creamos un objeto f como manejador.

Uso de contextos

Aunque estos contextos también se pueden utilizar en modo lectura, son realmente importantes cuando escribimos datos en un fichero, ya que nos aseguran el cierre del mismo y evitamos pérdidas de datos.

Añadiendo líneas

Añadir información a un fichero se podría ver como un caso especial de escribir información; con la diferencia de que no podemos modificar el contenido previo, sino únicamente añadir nuevos datos.

Para abrir un fichero en modo añadido utilizamos la función open() con el modificador 'a'.

Ejercicios

  1. pypas   wc
  2. pypas   read-csv
  3. pypas   txt2md
  4. pypas   avg-temps
  5. pypas   find-words
  6. pypas   sum-matrix
  7. pypas   longest-word
  8. pypas   word-freq
  9. pypas   get-line
  10. pypas   replace-chars
  11. pypas   histogram-txt
  12. pypas   submarine
  13. pypas   common-words