Manejo de archivos
Hasta ahora todos los datos del programa vivían en memoria: las variables, listas y diccionarios desaparecen en cuanto Python termina. Para que un programa pueda guardar o recuperar información entre ejecuciones, necesita leer y escribir archivos. Esta lección cubre las dos formas habituales de hacerlo: la función integrada open() y el módulo pathlib (más moderno).
La función open()
open() es la función integrada que abre un archivo y te devuelve un objeto que puedes leer o escribir. Recibe el nombre del archivo y el modo en el que quieres abrirlo:
f = open("notas.txt", "r") # "r" = read (lectura)
contenido = f.read()
f.close()
print(contenido)
El método .read() devuelve todo el contenido del archivo como una sola cadena. Después de usarlo es importante cerrar el archivo con .close(), para liberar el recurso al sistema operativo.
Nota: llamar manualmente a .close() es propenso al olvido, sobre todo si entre el open() y el close() ocurre un error que interrumpe el flujo. Por eso casi nunca lo verás escrito así en código real: se usa la construcción with, que cierra el archivo automáticamente.
La construcción with
Esta es la forma idiomática y recomendada de trabajar con archivos en Python:
with open("notas.txt", "r") as f:
contenido = f.read()
print(contenido) # f ya está cerrado aquí
El bloque with abre el archivo, te lo deja disponible bajo el nombre f, y lo cierra automáticamente al salir del bloque, incluso si dentro del bloque se produce un error. Es el patrón estándar para cualquier recurso que requiera apertura y cierre: archivos, conexiones de red, conexiones a base de datos.
Modos de apertura
El segundo argumento de open() indica para qué quieres el archivo:
| Modo | Significado |
|---|---|
"r" | Lectura. El archivo debe existir; si no, lanza FileNotFoundError. Es el modo por defecto. |
"w" | Escritura. Si el archivo existe, borra su contenido. Si no existe, lo crea. |
"a" | Append: añadir al final. Si el archivo existe, escribe al final sin borrar lo anterior. Si no existe, lo crea. |
"r+" | Lectura y escritura. El archivo debe existir. |
"x" | Creación exclusiva. Falla si el archivo ya existe. Útil para evitar pisar datos por accidente. |
"b" | Modo binario (se añade al modo: "rb", "wb"). Para imágenes, PDF, archivos comprimidos, etc. |
Nota: ojo con el modo "w": vacía el archivo silenciosamente en cuanto se abre, antes incluso de escribir nada. Si tu intención era añadir contenido, usa "a". Es uno de los errores que más datos ha hecho perder por accidente.
Leer un archivo
Hay varias formas de leer, según lo que necesites:
with open("notas.txt", "r") as f:
texto = f.read() # todo el archivo como una sola cadena
with open("notas.txt", "r") as f:
primera = f.readline() # solo la primera línea (incluye el \n)
with open("notas.txt", "r") as f:
lineas = f.readlines() # lista de cadenas, una por línea
Pero la forma más idiomática, sobre todo para archivos grandes, es iterar directamente sobre el objeto del archivo. Python lee línea a línea, sin cargar todo en memoria:
with open("notas.txt", "r") as f:
for linea in f:
print(linea.rstrip()) # rstrip quita el \n del final
Funciona porque los objetos de archivo son iterables: for sabe pedirles una línea cada vez, exactamente igual que cuando recorre una lista.
Escribir un archivo
Para escribir se usa .write(), que toma una cadena y la añade en la posición actual del archivo. Recuerda añadir \n al final de cada línea si quieres saltos: .write() no los añade por ti.
with open("salida.txt", "w") as f:
f.write("primera línea\n")
f.write("segunda línea\n")
Si tienes varias líneas en una lista, .writelines() escribe todas de golpe:
lineas = ["uno\n", "dos\n", "tres\n"]
with open("salida.txt", "w") as f:
f.writelines(lineas)
Para añadir al final de un archivo existente, usa el modo "a":
with open("log.txt", "a") as f:
f.write("nueva entrada\n")
Una alternativa muy común es usar print() redirigiéndolo al archivo con el argumento file=. Tiene la ventaja de añadir el salto de línea automáticamente:
with open("salida.txt", "w") as f:
print("primera línea", file=f)
print("valor:", 42, file=f)
Codificación de caracteres
Los archivos de texto necesitan saber cómo se codifican los caracteres en bytes. En Python moderno se asume UTF-8 en casi todos los sistemas, pero conviene declararlo explícitamente para que el programa funcione igual en Windows, Linux o Mac:
with open("notas.txt", "r", encoding="utf-8") as f:
texto = f.read()
Nota: si abres un archivo y los acentos o las eñes aparecen como caracteres raros (ñ, é...), casi seguro es un problema de codificación. Prueba a abrirlo con encoding="latin-1" o pide a quien lo generó que lo guarde como UTF-8.
El módulo pathlib
pathlib es la forma moderna de manejar rutas en Python. Representa cada ruta como un objeto Path con métodos cómodos. Para muchas operaciones simples sustituye a open() con menos código:
from pathlib import Path
ruta = Path("notas.txt")
# leer todo el archivo en una sola línea
texto = ruta.read_text(encoding="utf-8")
# escribir todo el archivo en una sola línea
ruta.write_text("hola, mundo\n", encoding="utf-8")
Path también te da operaciones útiles sobre las rutas sin tocar el archivo en sí:
>>> p = Path("/var/www/ignit/index.php")
>>> p.exists() # ¿existe el archivo?
True
>>> p.is_file()
True
>>> p.is_dir()
False
>>> p.name # nombre con extensión
'index.php'
>>> p.stem # nombre sin extensión
'index'
>>> p.suffix
'.php'
>>> p.parent # carpeta contenedora
PosixPath('/var/www/ignit')
>>> p.parent / "config.php" # construir rutas con el operador /
PosixPath('/var/www/ignit/config.php')
El operador / aplicado a un Path compone rutas de forma multiplataforma: no te tienes que preocupar de si el separador del sistema es / o \.
Listar archivos de una carpeta
Para recorrer los archivos de una carpeta, pathlib ofrece .iterdir() y .glob():
from pathlib import Path
# todo lo que hay en la carpeta actual
for elemento in Path(".").iterdir():
print(elemento)
# solo los archivos .txt
for archivo in Path(".").glob("*.txt"):
print(archivo.name)
# recursivo: archivos .py en cualquier subcarpeta
for archivo in Path(".").rglob("*.py"):
print(archivo)
glob() usa los mismos patrones de comodines que la línea de comandos: * cualquier secuencia, ? un único carácter, ** cualquier nivel de subdirectorios.
Archivos estructurados: CSV y JSON
Para archivos con estructura conocida, la stdlib trae módulos especializados que evitan tener que parsear el contenido a mano.
Para CSV (valores separados por comas), el módulo csv:
import csv
with open("ventas.csv", "r", encoding="utf-8") as f:
lector = csv.DictReader(f)
for fila in lector:
print(fila["producto"], "-", fila["precio"])
DictReader usa la primera línea del CSV como nombres de columna y te entrega cada fila como un diccionario. Mucho más cómodo que parsear con .split(",").
Para JSON, el módulo json:
import json
from pathlib import Path
# leer
datos = json.loads(Path("config.json").read_text(encoding="utf-8"))
print(datos["host"])
# escribir
nueva_config = {"host": "localhost", "puerto": 5432}
Path("config.json").write_text(
json.dumps(nueva_config, indent=2),
encoding="utf-8",
)
json.loads() convierte una cadena JSON en estructuras Python (diccionarios, listas, números). json.dumps() hace la conversión inversa. El argumento indent=2 formatea el JSON de salida con sangrado, mucho más legible.
Nota: no confundas json.loads (de string) con json.load (de un archivo abierto). Pasa lo mismo con dumps y dump. Los dos pares funcionan, pero hoy en día con pathlib es casi siempre más cómodo trabajar con strings y dejar la lectura del archivo a Path.read_text().
¿Y si el archivo no existe?
Hasta ahora todos los ejemplos asumen que el archivo está donde dice estar. En la práctica eso falla constantemente: el usuario escribe mal la ruta, el archivo se ha movido, no tienes permisos para leerlo... En todos esos casos Python lanza una excepción.
>>> with open("no-existe.txt", "r") as f:
... texto = f.read()
FileNotFoundError: [Errno 2] No such file or directory: 'no-existe.txt'
Cómo capturar esos errores sin que el programa se caiga es justo el tema de la próxima lección.
En la próxima lección: excepciones: capturar errores con try/except, los tipos más comunes, y cuándo conviene relanzarlos.