Leer y escribir ficheros de texto en Python
Hasta ahora, los datos de tus programas han vivido en la RAM: existen mientras el programa corre y desaparecen cuando termina. Los ficheros rompen esa barrera. Son el mecanismo más básico y universal de persistencia: lo que escribes en un fichero de texto sobrevive a tu programa, al reinicio del sistema y al paso del tiempo. Antes de hablar de bases de datos, APIs o formatos sofisticados, hay que entender bien este nivel fundacional.
¿Por qué escribir en disco?
Piensa en tu programa como alguien que trabaja con un cuaderno de notas. Mientras trabaja, el cuaderno está abierto sobre la mesa y puede leer y añadir anotaciones con facilidad. Cuando esa persona se va a casa, el cuaderno permanece en la mesa: las notas siguen ahí al día siguiente, aunque la persona no esté.
Un fichero de texto es ese cuaderno. Tu programa puede abrirlo, leer lo que hay, escribir algo nuevo y cerrarlo. La próxima vez que el programa se ejecute —o que otro programa lo abra— toda la información seguirá intacta.
Los casos de uso son innumerables: guardar la configuración de una aplicación, registrar eventos en un log, exportar resultados a CSV, leer datos de entrada, almacenar el estado de un juego... Todo esto pasa por leer y escribir ficheros. Dominar open(), los modos y el gestor de contexto with es, sin exageración, imprescindible.
open() y los modos de apertura
La función open() es el punto de entrada a cualquier operación con ficheros. Devuelve un file object: un objeto que representa el canal abierto entre tu programa y el fichero en disco.
f = open('datos.txt', 'r', encoding='utf-8')
Los tres parámetros más importantes son:
- Ruta del fichero: puede ser relativa al directorio de trabajo o absoluta.
- Modo de apertura: controla qué operaciones están permitidas y qué ocurre si el fichero ya existe.
- encoding: la codificación de caracteres. Siempre especifícala explícitamente (más sobre esto en la sección de encoding).
La tabla de modos es pequeña pero importa conocerla bien:
| Modo | Nombre | Lee | Escribe | Si no existe | Si existe |
|---|---|---|---|---|---|
'r' | Read | ✅ | ❌ | FileNotFoundError | Abre tal cual |
'w' | Write | ❌ | ✅ | Crea nuevo | Borra todo el contenido |
'a' | Append | ❌ | ✅ | Crea nuevo | Añade al final |
'x' | eXclusive | ❌ | ✅ | Crea nuevo | FileExistsError |
'r+' | Read+Write | ✅ | ✅ | FileNotFoundError | Abre tal cual |
'rb' / 'wb' | Binary | Igual que r/w pero en modo binario (bytes, no texto) | |||
El modo que más muerdes por no conocer bien es 'w'. Si abres un fichero con 'w' y ese fichero ya tiene contenido, ese contenido desaparece inmediatamente, antes de que escribas nada. Si quieres añadir registros a un log sin borrar los anteriores, siempre usa 'a'.
open('archivo.txt') sin especificar modo, Python asume 'r'. Funciona para leer, pero fallará si el fichero no existe.
Leer el contenido
Con el fichero abierto en modo lectura, tienes varias formas de consumir su contenido dependiendo del caso de uso:
read() — todo de una vez
La forma más directa: carga el fichero completo en una cadena de texto.
with open('datos.txt', 'r', encoding='utf-8') as f:
contenido = f.read() # str con todo el texto
print(contenido)
Puedes pasarle un número para leer solo esa cantidad de caracteres:
with open('datos.txt', 'r', encoding='utf-8') as f:
fragmento = f.read(100) # primeros 100 caracteres
Útil para ficheros pequeños o cuando necesitas el texto completo para procesarlo. Para ficheros grandes (gigabytes), no es buena idea: cargarías todo en RAM.
readline() y readlines() — línea a línea
with open('datos.txt', 'r', encoding='utf-8') as f:
primera = f.readline() # lee una línea, incluye el '\n' final
segunda = f.readline() # continúa donde lo dejó
with open('datos.txt', 'r', encoding='utf-8') as f:
lineas = f.readlines() # lista de strings, uno por línea
# ['línea 1\n', 'línea 2\n', 'línea 3\n']
Iteración directa — la forma más eficiente
El file object es iterable. Puedes recorrerlo con un for sin cargar el fichero completo en memoria. Para ficheros grandes, esta es la opción correcta:
with open('datos.txt', 'r', encoding='utf-8') as f:
for linea in f:
linea = linea.rstrip('\n') # quita el salto de línea del final
print(linea)
Python lee una línea a la vez del buffer interno, lo que hace que esta técnica sea eficiente con ficheros de cientos de megabytes.
with garantiza el cierre aunque ocurra una excepción.Escribir y añadir texto
Para escribir necesitas abrir en modo 'w' (sobrescribir) o 'a' (añadir):
# Crear o sobrescribir completamente
with open('resultado.txt', 'w', encoding='utf-8') as f:
f.write('Primera línea\n')
f.write('Segunda línea\n')
# Añadir al final sin borrar lo que había
with open('log.txt', 'a', encoding='utf-8') as f:
f.write('2026-03-07 14:00 — Proceso completado\n')
Hay dos cosas que trips a casi todo el mundo al principio:
write()no añade salto de línea automáticamente. Si quieres cada dato en su propia línea, debes añadir\nexplícitamente al final de cada string.write()solo acepta strings. Si tienes números u otros tipos, conviértelos primero:f.write(str(42) + '\n').
Para escribir una lista de strings de golpe, writelines() es más conveniente:
lineas = ['manzana\n', 'pera\n', 'naranja\n']
with open('frutas.txt', 'w', encoding='utf-8') as f:
f.writelines(lineas)
# Resultado: fichero con tres líneas
Nota que writelines() tampoco añade \n entre elementos: los \n deben estar ya en los strings de la lista.
Una alternativa elegante es usar print() con el parámetro file=, que sí añade salto de línea automáticamente:
with open('resultado.txt', 'w', encoding='utf-8') as f:
print('Línea 1', file=f)
print('Línea 2', file=f)
print(42, 'datos procesados', file=f) # convierte automáticamente
Para procesos batch donde generas muchas líneas, esta combinación de print() + with es muy legible.
El gestor de contexto with
Si has leído los ejemplos anteriores con atención, habrás notado que todos usan with open(...) as f:. No es por estilo: es la forma correcta y prácticamente obligatoria de trabajar con ficheros en Python moderno.
El problema con el enfoque manual es que requiere disciplina:
# El enfoque manual — frágil
f = open('datos.txt', 'r', encoding='utf-8')
contenido = f.read()
# ¿Qué pasa si aquí se lanza una excepción?
f.close() # nunca se ejecuta si hay un error arriba
Si ocurre cualquier excepción entre el open() y el close(), el fichero queda abierto. En un programa largo esto agota los descriptores de fichero del sistema operativo. En ficheros de escritura, los datos del buffer pueden no haberse volcado a disco.
El gestor de contexto with resuelve esto de forma elegante:
# El enfoque correcto — with garantiza el cierre
with open('datos.txt', 'r', encoding='utf-8') as f:
contenido = f.read()
# cualquier excepción aquí → f.close() se llama igualmente
# Aquí f ya está cerrado, pase lo que pase
El with actúa como guardia: cuando el bloque termina (ya sea por éxito o por excepción), Python llama automáticamente a f.__exit__(), que a su vez llama a f.close(). No hay forma de que el fichero quede abierto.
with de Python es el mecanismo que garantiza que el «cajón» (fichero) se cierre siempre, incluso si ocurre un error mientras está abierto.También puedes abrir varios ficheros en el mismo with:
# Leer de uno y escribir en otro simultáneamente
with open('entrada.txt', 'r', encoding='utf-8') as origen, \
open('salida.txt', 'w', encoding='utf-8') as destino:
for linea in origen:
destino.write(linea.upper())
Codificación: siempre utf-8
La codificación de caracteres es uno de esos temas que parece aburrido hasta que tu código explota en producción con un UnicodeDecodeError porque el cliente tiene Windows en español.
Cuando lees o escribes texto, Python necesita saber cómo convertir bytes en caracteres (decodificar) o caracteres en bytes (codificar). Si no especificas encoding, usa la codificación por defecto del sistema:
- Linux / macOS → UTF-8 (generalmente)
- Windows → cp1252 o cp850 (dependiendo de la configuración regional)
El resultado es que el mismo fichero con tildes o eñes funciona en tu Mac pero falla en el Windows de producción. La solución es siempre especificarlo explícitamente:
# Bien — explícito y portable
with open('datos.txt', 'r', encoding='utf-8') as f:
contenido = f.read()
# También bien — para ficheros que sabes que vienen en otra codificación
with open('legacy.txt', 'r', encoding='latin-1') as f:
contenido = f.read()
Si tienes un fichero y no sabes su codificación, la librería chardet puede detectarla:
import chardet
with open('misterioso.txt', 'rb') as f: # binario para no decodificar
raw = f.read(4096) # muestra de los primeros 4 KB
detected = chardet.detect(raw)
print(detected['encoding']) # → 'utf-8', 'Windows-1252', etc.
Y si necesitas gestionar errores de codificación sin que el programa explote, el parámetro errors te da opciones:
open('f.txt', 'r', encoding='utf-8', errors='ignore') # omite caracteres inválidos
open('f.txt', 'r', encoding='utf-8', errors='replace') # sustituye con '?'
open('f.txt', 'r', encoding='utf-8', errors='backslashreplace') # escapa: \x9c
pathlib: rutas como objetos
La biblioteca estándar de Python incluye desde Python 3.4 el módulo pathlib, que trata las rutas de sistema de ficheros como objetos con métodos propios. Es la forma moderna de trabajar con rutas y, en muchos casos, también con ficheros.
from pathlib import Path
# Construir rutas de forma portable (funciona en Windows, Linux y macOS)
ruta = Path('datos') / 'resultados' / 'informe.txt'
# → PosixPath('datos/resultados/informe.txt') en Linux
# → WindowsPath('datos\\resultados\\informe.txt') en Windows
Lo más cómodo: para operaciones simples de lectura/escritura de ficheros pequeños, pathlib elimina el open() y el with:
p = Path('notas.txt')
# Leer el fichero completo
texto = p.read_text(encoding='utf-8')
# Escribir (sobrescribe si existe)
p.write_text('Contenido nuevo\nSegunda línea\n', encoding='utf-8')
# Verificar existencia antes de leer
if p.exists():
contenido = p.read_text(encoding='utf-8')
Los métodos útiles de navegación que más vas a usar:
p = Path('/home/usuario/proyecto/datos/notas.txt')
p.parent # → Path('/home/usuario/proyecto/datos')
p.name # → 'notas.txt'
p.stem # → 'notas'
p.suffix # → '.txt'
p.is_file() # → True
p.is_dir() # → False
p.stat().st_size # → tamaño en bytes
# Crear directorio (y padres si no existen)
Path('output/logs/2026').mkdir(parents=True, exist_ok=True)
# Renombrar / mover
p.rename(Path('datos/notas_backup.txt'))
Y para buscar ficheros en un directorio, glob() es imbatible:
directorio = Path('datos')
# Todos los .txt en el directorio
for fichero in directorio.glob('*.txt'):
print(fichero.name, fichero.stat().st_size, 'bytes')
# Recursivo: todos los .py en el proyecto y sus subdirectorios
for fichero in Path('.').rglob('*.py'):
print(fichero)
¿Cuándo usar pathlib y cuándo open()? Para leer o escribir un fichero completo en una operación, pathlib es más conciso. Para leer línea a línea o escribir en bucle, sigue usando open() con with. Para trabajar con rutas siempre usa pathlib en lugar del antiguo os.path. Las dos aproximaciones se complementan sin problema.
Errores frecuentes
Python es bastante explícito con los errores de ficheros. Los tres que vas a ver con más frecuencia:
FileNotFoundError
try:
with open('config.txt', 'r', encoding='utf-8') as f:
config = f.read()
except FileNotFoundError:
print('El fichero no existe. Usando configuración por defecto.')
config = ''
La alternativa con pathlib para evitar el try/except cuando solo necesitas verificar:
p = Path('config.txt')
config = p.read_text(encoding='utf-8') if p.exists() else ''
PermissionError
try:
with open('/etc/shadow', 'r', encoding='utf-8') as f:
datos = f.read()
except PermissionError:
print('Sin permisos para leer este fichero.')
Típico en sistemas Linux/macOS con ficheros protegidos, o en Windows cuando otro proceso tiene el fichero bloqueado en modo exclusivo.
UnicodeDecodeError
try:
with open('datos_legacy.txt', 'r', encoding='utf-8') as f:
contenido = f.read()
except UnicodeDecodeError:
# El fichero no está en UTF-8. Intentar con Latin-1
with open('datos_legacy.txt', 'r', encoding='latin-1') as f:
contenido = f.read()
El UnicodeDecodeError ocurre cuando los bytes del fichero no corresponden a la codificación especificada. Si no tienes control sobre el origen del fichero, el enfoque de detectar y reintentar con chardet (visto en la sección anterior) es más robusto.
'rb') e imprime los primeros bytes. Eso te dice exactamente qué hay ahí antes de intentar decodificar.
❓ Preguntas frecuentes
❓ Preguntas frecuentes sobre Leer y escribir ficheros de texto en Python
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Leer y escribir ficheros de texto en Python? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!