Módulo 1 · Lección 44 de 59
Leer ficheros de texto
Cómo abrir un fichero con with, por qué hay que indicar siempre el encoding y cuál de las tres formas de leer conviene.
Hasta ahora todos tus programas empezaban de cero: los datos entraban por input() y desaparecían al terminar. Los programas de verdad leen de donde ya hay información guardada, y el sitio más común es un fichero de texto en el disco. Una lista de la compra, un registro de lo que ha pasado, un exportado de la contabilidad.
Leer un fichero son tres pasos: abrirlo, leerlo y cerrarlo. El tercero es el que se olvida, y Python tiene una construcción que lo hace por ti.
Abrir un fichero: with open#
Parte de este fichero, que puedes crear con tu editor en la misma carpeta que el programa:
plátanos
leche desnatada
café molido
pan de centeno
aceite de olivaY este programa, que lo lee entero:
with open("compra.txt", encoding="utf-8") as fichero:
contenido = fichero.read()
print(contenido)plátanos
leche desnatada
café molido
pan de centeno
aceite de olivaLa línea en blanco del final no es un fallo: el fichero acaba con un salto de línea, y print añade otro.
Desmenuza la primera línea, porque tiene cuatro cosas:
open("compra.txt")abre el fichero y devuelve un objeto con el que se lee.encoding="utf-8"dice cómo están codificadas las letras. Enseguida vemos por qué es obligatorio.as ficheroguarda ese objeto en una variable. El nombre lo eliges tú; verás muchof, peroficherose lee mejor.- El bloque sangrado es donde trabajas con él. Al salir del bloque, el fichero se cierra solo.
Por qué siempre with#
Se puede abrir un fichero sin with, pero entonces tienes que acordarte de cerrarlo:
fichero = open("compra.txt", encoding="utf-8")
contenido = fichero.read()
fichero.close() # si esta línea no se ejecuta, el fichero queda abiertoEl problema es ese comentario. Si entre el open y el close salta un error, la línea del close nunca se ejecuta y el fichero se queda abierto. Un fichero abierto consume un recurso del sistema, y en Windows además queda bloqueado: no podrás borrarlo ni renombrarlo, y si estabas escribiendo, parte de lo escrito puede perderse sin llegar al disco.
with garantiza el cierre pase lo que pase, incluso si el bloque revienta a la mitad. Puedes comprobarlo:
with open("compra.txt", encoding="utf-8") as fichero:
contenido = fichero.read()
print(fichero.closed)TrueEl encoding, y por qué no es opcional#
Un fichero de texto no guarda letras: guarda bytes. La tabla que traduce esos bytes a letras se llama codificación, y hoy la que se usa en todas partes es UTF-8, que sabe representar acentos, eñes, símbolos y emojis.
El problema es que si no dices nada, Python usa la codificación por defecto del sistema. En macOS y Linux eso ya es UTF-8, así que parece que funciona. En Windows, en cambio, en muchas configuraciones sigue siendo cp1252, y entonces el mismo programa que funcionaba en tu portátil falla en el de al lado, o peor, lee mal las tildes sin avisar.
# mal: depende del sistema donde se ejecute
with open("compra.txt") as fichero:
...
# bien: se comporta igual en todas partes
with open("compra.txt", encoding="utf-8") as fichero:
...Las tres formas de leer#
read(), el fichero entero en una cadena#
with open("compra.txt", encoding="utf-8") as fichero:
contenido = fichero.read()
print(type(contenido))
print(len(contenido))<class 'str'>
68Te da una única cadena con todo dentro, saltos de línea incluidos. Es lo más cómodo cuando el fichero es pequeño y quieres tratarlo como un bloque de texto: buscar algo con in, contar palabras, reemplazar.
readlines(), una lista de líneas#
with open("compra.txt", encoding="utf-8") as fichero:
lineas = fichero.readlines()
print(lineas)
print(len(lineas))['plátanos\n', 'leche desnatada\n', 'café molido\n', 'pan de centeno\n', 'aceite de oliva\n']
5Mira bien la lista: cada elemento acaba en \n. Ese salto de línea es parte del texto del fichero y viene incluido, lo que sorprende siempre la primera vez. Si comparas linea == "plátanos" te dirá que no, porque lo que tienes es "plátanos\n".
La solución es strip(), que quita los espacios y saltos de los dos extremos, aplicado a cada línea. Pero si lo que quieres es directamente la lista limpia, hay un camino más corto:
with open("compra.txt", encoding="utf-8") as fichero:
lineas = fichero.read().splitlines()
print(lineas)['plátanos', 'leche desnatada', 'café molido', 'pan de centeno', 'aceite de oliva']splitlines() sobre el contenido completo es el atajo directo para tener la lista ya limpia, sin pasar por readlines() ni recortar nada a mano.
El bucle for, línea a línea#
Esta es la forma buena, y la que deberías usar por defecto:
with open("compra.txt", encoding="utf-8") as fichero:
numero = 1
for linea in fichero:
print(f"{numero}. {linea.strip()}")
numero += 11. plátanos
2. leche desnatada
3. café molido
4. pan de centeno
5. aceite de olivaRecorrer el fichero con un for directamente, sin llamar a nada, te va entregando una línea cada vez. La diferencia con las otras dos es de memoria: read() y readlines() cargan el fichero entero de golpe, así que un registro de dos gigas ocupa dos gigas de memoria RAM, y el programa se arrastra o directamente muere. El for solo tiene una línea en memoria en cada momento, da igual lo grande que sea el fichero.
| Forma | Qué devuelve | Cuándo usarla |
|---|---|---|
fichero.read() | Una cadena con todo | Ficheros pequeños que tratas como un bloque |
fichero.read().splitlines() | Lista de líneas sin \n | Ficheros pequeños, cuando quieres la lista limpia |
fichero.readlines() | Lista de líneas con \n | Casi nunca; prefiere las otras dos |
for linea in fichero: | Una línea cada vez | Por defecto, y obligatorio si el fichero es grande |
Cuando el fichero no está#
Si la ruta no existe, open() no devuelve nada raro ni None: detiene el programa.
with open("compras.txt", encoding="utf-8") as fichero:
print(fichero.read())Traceback (most recent call last):
File "/Users/erwin/curso-python/lectura.py", line 1, in <module>
with open("compras.txt", encoding="utf-8") as fichero:
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'compras.txt'En la lección de manejo de errores verás cómo capturar esto con try para que el programa siga con un mensaje amable en vez de morirse.
Un ejemplo completo#
Un fichero de tareas donde las líneas que empiezan por # son comentarios y las vacías se ignoran:
# Tareas de la semana
Revisar el presupuesto
Llamar a la gestoría
Preparar la reunión del martes
# Pendiente de confirmar
Actualizar la página de contactodef leer_tareas(ruta: str) -> list[str]:
"""Devuelve las tareas del fichero, sin comentarios ni líneas vacías."""
tareas = []
with open(ruta, encoding="utf-8") as fichero:
for linea in fichero:
limpia = linea.strip()
if limpia == "" or limpia.startswith("#"):
continue
tareas.append(limpia)
return tareas
tareas = leer_tareas("tareas.txt")
print(f"Hay {len(tareas)} tareas pendientes:")
for tarea in tareas:
print(f" - {tarea}")
mas_larga = max(tareas, key=len)
print(f"La de descripción más larga es: {mas_larga}")Hay 4 tareas pendientes:
- Revisar el presupuesto
- Llamar a la gestoría
- Preparar la reunión del martes
- Actualizar la página de contacto
La de descripción más larga es: Actualizar la página de contactoFíjate en el patrón, porque lo vas a repetir muchas veces: una función que recibe la ruta, abre con with, recorre con for, limpia con strip(), descarta lo que no sirve con continue y devuelve una lista ya lista para usar. El resto del programa no sabe nada de ficheros.