¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Archivos y persistencia
Guardar y recuperar datos entre ejecuciones.
Este módulo forma parte del curso de programación desde cero con Python
Módulo 13. Archivos y persistencia
Hasta ahora, todos tus programas han vivido en la memoria RAM: al cerrar el programa, los datos desaparecen. Para que la información sobreviva entre ejecuciones, necesitas persistencia, y la forma más directa de lograrla es guardar los datos en archivos. Este módulo te enseña a leer y escribir archivos de texto, a gestionar rutas de forma portable entre Windows y Linux con pathlib, a trabajar con formatos estructurados como CSV y JSON, y a usar gestores de contexto (with) para no dejar recursos abiertos. También conocerás las novedades de Python 3.15 que afectan directamente a la persistencia: UTF-8 como codificación predeterminada y mejoras en módulos como shelve.
1. Título y alcance
Título: Archivos y persistencia: guardar y recuperar datos entre ejecuciones.
Alcance: Comprender qué es un archivo y por qué es necesario para la persistencia; abrir archivos en modo lectura, escritura y añadido; usar gestores de contexto (with); gestionar rutas de forma portable con pathlib; leer y escribir texto con codificación UTF-8 (predeterminada en Python 3.15); trabajar con CSV y JSON; conocer alternativas de persistencia como shelve (con su nuevo método reorganize()) y pickle; y manejar errores de archivos y permisos con try/except. Se presupone el manejo de excepciones (Módulo 11), funciones (Módulo 9) y colecciones (Módulo 8). No se profundiza en bases de datos relacionales (se introducirá sqlite3 en el Módulo 20).
2. Objetivos de aprendizaje
Al terminar este módulo serás capaz de:
1. Explicar qué es un archivo, qué es una ruta y por qué la persistencia es esencial.
2. Abrir archivos con open() en los modos "r", "w", "a" y "x", y entender qué hace cada uno.
3. Leer un archivo completo, línea a línea, o en fragmentos.
4. Escribir y añadir texto a un archivo, garantizando que se cierre correctamente con with.
5. Construir rutas portables con pathlib.Path, incluyendo rutas absolutas, relativas y la resolución de ~ (directorio personal).
6. Leer y escribir CSV con el módulo csv, y JSON con el módulo json.
7. Comprender la codificación de caracteres y por qué UTF-8 es el valor predeterminado en Python 3.15.
8. Manejar FileNotFoundError, PermissionError y UnicodeDecodeError de forma específica.
9. Conocer shelve como almacén clave-valor persistente y su nuevo método reorganize() en Python 3.15.
10. Aplicar buenas prácticas: usar with, no cargar archivos gigantes en memoria, validar rutas y no confiar en la codificación del sistema.
3. Conocimientos previos
- Del Módulo 8: Listas, diccionarios y comprensiones.
- Del Módulo 9: Funciones, return y docstrings.
- Del Módulo 11: Excepciones, try/except y logging.
- Del Módulo 12: Módulos e importaciones (usarás import csv, import json, from pathlib import Path).
- Ningún conocimiento de archivos, rutas ni persistencia.
4. Introducción motivadora: el programa que olvida todo al cerrarse
Imagina una agenda de contactos que, al cerrar la aplicación, pierde todos los nombres y teléfonos introducidos. No es una agenda: es un formulario temporal. Lo mismo ocurre con cualquier programa que gestiona datos: sin persistencia, cada ejecución empieza de cero. Los archivos son el mecanismo más sencillo y universal para que la información sobreviva. Y no solo para programas de escritorio: los archivos de configuración, los registros (logs), los datos exportados y los archivos CSV que comparten otras herramientas son parte del día a día de cualquier desarrollador.
Python ofrece herramientas de alto nivel para trabajar con archivos sin pelearse con los detalles del sistema operativo. pathlib unifica las rutas de Windows y Linux; with garantiza que los archivos se cierren incluso si hay errores; y los módulos csv y json evitan que tengas que parsear formatos a mano. En este módulo aprenderás a usar todo ello con seguridad y buenas prácticas.
5. Explicación paso a paso
5.1. ¿Qué es un archivo y qué es una ruta?
Un archivo es una secuencia de bytes almacenada en un dispositivo (disco, SSD, etc.) que el sistema operativo identifica mediante un nombre y una ruta. La ruta puede ser:
- Absoluta: describe la ubicación completa desde la raíz del sistema.
- Windows:
C:\Users\Ana\documentos\datos.txt - Linux:
/home/ana/documentos/datos.txt
- Relativa: describe la ubicación respecto al directorio de trabajo actual.
datos.txt(en la carpeta actual)../datos.txt(en la carpeta padre)
[WINDOWS / LINUX] Windows usa la barra invertida\y distingue unidades (C:,D:). Linux usa la barra normal/y tiene una única raíz/. Escribir rutas manualmente con el separador del sistema es una fuente constante de errores de portabilidad.pathlibresuelve este problema.
5.2. `pathlib`: rutas portables
pathlib es el módulo de la biblioteca estándar para manipular rutas como objetos. La clase principal es Path.
from pathlib import Path # Ruta relativa ruta = Path("datos") / "contactos.txt" # Ruta absoluta (depende del sistema) ruta_abs = Path.cwd() / "datos" / "contactos.txt" # Directorio personal del usuario ruta_home = Path.home() / "documentos" / "notas.txt" print(ruta) # datos/contactos.txt (en ambos sistemas) print(ruta_abs) # /home/ana/proyecto/datos/contactos.txt o C:\...\datos\contactos.txt print(ruta_home) # /home/ana/documentos/notas.txt o C:\Users\Ana\documentos\notas.txt
El operador / une segmentos de ruta de forma portable: en Windows produce \ y en Linux produce / automáticamente. No necesitas preocuparte por el separador.
Operaciones útiles con Path:
| Operación | Ejemplo | Resultado |
|---|---|---|
| Comprobar existencia | ruta.exists() | True o False |
| Comprobar si es archivo | ruta.is_file() | True o False |
| Comprobar si es directorio | ruta.is_dir() | True o False |
| Obtener nombre | ruta.name | "contactos.txt" |
| Obtener extensión | ruta.suffix | ".txt" |
| Obtener padre | ruta.parent | Path("datos") |
| Crear directorio | ruta.mkdir(parents=True, exist_ok=True) | Crea la carpeta si no existe |
| Listar contenido | list(ruta.parent.iterdir()) | Lista de rutas hijas |
[PYTHON]Pathes un objeto, no una cadena. Puedes pasarlo aopen()directamente; Python lo convierte a la ruta del sistema automáticamente. Esto evita concatenaciones manuales y errores de separador. En Python 3.15, se eliminaPurePath.is_reserved()(deprecado desde 3.13); si necesitas comprobar si una ruta está reservada en Windows, usaos.path.isreserved().
[ERROR HABITUAL] UsarPath("datos") / "contactos.txt"y luego intentar concatenar con+:ruta + ".txt"falla porquePathno admite+con cadenas. Usaruta.with_suffix(".txt")o construye la ruta desde el principio.
5.3. `open()` y los modos de apertura
La función incorporada open() abre un archivo y devuelve un objeto de archivo (file object). Su firma básica es:
open(ruta, mode="r", encoding=None)
Los modos principales son:
| Modo | Significado | ¿Crea el archivo? | ¿Trunca el contenido? |
|---|---|---|---|
"r" | Lectura (predeterminado). | No (falla si no existe). | No |
"w" | Escritura. | Sí. | Sí (vacía el archivo antes de escribir). |
"a" | Añadido (append). | Sí. | No (escribe al final). |
"x" | Creación exclusiva. | Sí. | Falla si el archivo ya existe. |
"b" | Modo binario (se combina: "rb", "wb"). | — | — |
"+" | Lectura y escritura (se combina: "r+", "w+"). | — | — |
[ERROR HABITUAL] Abrir un archivo en modo"w"cuando solo querías leerlo."w"vacía el archivo inmediatamente, antes de que escribas nada. Si el archivo contenía datos, los pierdes. Para leer, usa"r"; para añadir sin borrar, usa"a".
[BUENA PRÁCTICA] Especifica siempreencoding="utf-8"al abrir archivos de texto, incluso aunque Python 3.15 use UTF-8 por defecto. Ser explícito documenta la intención y protege el código si se ejecuta en un entorno con una configuración regional diferente. La guía de Python 3.15 recomienda auditar el código que abre archivos sinencodingpara evitar sorpresas al migrar.
5.4. Gestores de contexto: `with`
El objeto de archivo debe cerrarse después de usarlo. Si no se cierra, los datos pueden no escribirse completamente en el disco, o el archivo puede quedar bloqueado. La forma segura e idiomática de garantizar el cierre es usar with:
with open("datos.txt", "w", encoding="utf-8") as archivo: archivo.write("Hola, mundo\n") # Al salir del bloque, el archivo se cierra automáticamente, # incluso si se produce una excepción.
Sin with, tendrías que llamar a archivo.close() manualmente y asegurarte de que se ejecute incluso en caso de error (con try/finally). with elimina esa carga.
[PYTHON]withes un gestor de contexto (context manager). Cualquier objeto que implemente los métodos especiales__enter__y__exit__puede usarse conwith. Los archivos son el ejemplo más común, pero también se usan para conexiones de red, bloqueos, transacciones de bases de datos, etc. En el Módulo 15 profundizarás en cómo crear tus propios gestores de contexto.
[ERROR HABITUAL] Usar el archivo fuera del bloquewith:
with open("datos.txt") as f: contenido = f.read() print(f.read()) # ValueError: I/O operation on closed file
El archivo está cerrado fuera del bloque. Guarda lo que necesites dentro delwith.
5.5. Lectura de archivos
Leer todo el contenido de una vez:
with open("datos.txt", "r", encoding="utf-8") as f: contenido = f.read() print(contenido)
Leer línea a línea (recomendado para archivos grandes):
with open("datos.txt", "r", encoding="utf-8") as f: for linea in f: print(linea.rstrip("\n")) # Elimina el salto de línea
Iterar sobre el archivo es perezoso: no carga todo el contenido en memoria, lo que permite procesar archivos de gigabytes sin agotar la RAM.
Leer todas las líneas en una lista:
with open("datos.txt", "r", encoding="utf-8") as f: lineas = f.readlines()
[BUENA PRÁCTICA] Para archivos grandes, itera línea a línea en lugar de usar.read()o.readlines()..read()carga todo en memoria;.readlines()crea una lista con todas las líneas. La iteración directa (for linea in f) es la opción más eficiente en memoria.
[ERROR HABITUAL] Olvidar que cada línea leída incluye el carácter de salto de línea\nal final. Usa.rstrip("\n")o.strip()para eliminarlo antes de procesar el texto.
5.6. Escritura de archivos
Escribir (sobrescribe el archivo):
with open("salida.txt", "w", encoding="utf-8") as f: f.write("Línea 1\n") f.write("Línea 2\n")
Añadir (conserva el contenido existente):
with open("salida.txt", "a", encoding="utf-8") as f: f.write("Línea 3\n")
[PYTHON].write()no añade un salto de línea automáticamente. Debes incluirlo explícitamente (\n) si quieres que cada escritura ocupe una línea distinta.print()sí añade un salto, pero no escribe en archivos; escribe en la salida estándar. Para escribir con formato, puedes usarf.write(f"{valor}\n").
5.7. Codificación de caracteres y UTF-8
La codificación es la regla que traduce caracteres (como ñ, á, 中) a bytes. Si lees un archivo con una codificación distinta a la que se usó para escribirlo, obtendrás caracteres corruptos (mojibake) o un UnicodeDecodeError.
[PYTHON] Python 3.15 hace que UTF-8 sea la codificación predeterminada para la entrada/salida de texto en todas las plataformas (PEP 686). Antes, Windows usaba a menudocp1252, lo que causaba que un archivo con tildes escrito en Linux se viera mal en Windows. Ahora,open("datos.txt")sinencodingusa UTF-8 en ambos sistemas, lo que elimina la principal fuente de problemas de portabilidad.
Aun así, sé explícito: encoding="utf-8" documenta la intención y protege el código si se ejecuta en un entorno donde el modo UTF-8 está desactivado (PYTHONUTF8=0).
[ERROR HABITUAL]UnicodeDecodeErroral leer un archivo que no está en UTF-8 (por ejemplo, un CSV exportado por una herramienta antigua enlatin-1). Solución: especifica la codificación correcta (encoding="latin-1") o convierte el archivo a UTF-8 antes de procesarlo. En CSV generados en Windows con BOM, puede ser necesario usarencoding="utf-8-sig".
5.8. CSV: datos tabulares
El módulo csv de la biblioteca estándar lee y escribe archivos de valores separados por comas. Es el formato habitual para exportar e importar datos de hojas de cálculo y bases de datos.
Leer un CSV:
import csv with open("datos.csv", newline="", encoding="utf-8") as f: lector = csv.reader(f) for fila in lector: print(fila) # Cada fila es una lista de cadenas
Leer como diccionarios (con cabecera):
with open("datos.csv", newline="", encoding="utf-8") as f: lector = csv.DictReader(f) for fila in lector: print(fila["nombre"], fila["edad"])
Escribir un CSV:
with open("salida.csv", "w", newline="", encoding="utf-8") as f: escritor = csv.writer(f) escritor.writerow(["nombre", "edad"]) escritor.writerow(["Ana", 25]) escritor.writerow(["Luis", 30])
[PYTHON] El argumentonewline=""es importante al trabajar con CSV: evita que el módulocsvañada saltos de línea extra en Windows. En Python 3.15,csv.Sniffer(la herramienta para detectar el delimitador) es hasta un 1,6 veces más rápida, lo que mejora el rendimiento al procesar archivos grandes. Además, las reglas de quotingQUOTE_NOTNULLyQUOTE_STRINGS(introducidas en versiones anteriores) ya se aplican de forma consistente en el lector y el escritor.
[ERROR HABITUAL] Abrir un CSV sinnewline=""en Windows puede producir líneas en blanco entre filas al escribir. Siempre incluyenewline=""al abrir archivos CSV para escritura.
5.9. JSON: datos estructurados
JSON (JavaScript Object Notation) es un formato de texto ligero para intercambiar datos estructurados. Es el estándar de facto en APIs y archivos de configuración.
Leer JSON:
import json with open("datos.json", "r", encoding="utf-8") as f: datos = json.load(f) print(datos["nombre"]) print(datos["intereses"])
Escribir JSON:
import json datos = { "nombre": "Ana", "edad": 25, "intereses": ["Python", "ajedrez"] } with open("datos.json", "w", encoding="utf-8") as f: json.dump(datos, f, ensure_ascii=False, indent=2)
- ensure_ascii=False permite que los caracteres no ASCII (tildes, ñ, emojis) se escriban tal cual, en lugar de como secuencias \uXXXX.
- indent=2 formatea el JSON con sangría para que sea legible por humanos.
[PYTHON]jsonconvierte entre objetos Python y texto JSON: diccionarios ↔ objetos, listas ↔ arrays, cadenas ↔ strings, números ↔ numbers,True/False/None↔true/false/null. No todos los tipos de Python son serializables (por ejemplo, unsetno lo es); en esos casos,json.dumplanzaTypeError.
[AMPLIACIÓN] En Python 3.15, puedes usarlazy import jsonpara retrasar la carga del módulo hasta que se use por primera vez, lo que acelera el arranque de aplicaciones que solo necesitan JSON en ciertos caminos. También puedes declarar__lazy_modules__ = ["json"]para obtener el mismo efecto sin la palabra clave, manteniendo compatibilidad con versiones anteriores.
5.10. Otras formas de persistencia: `shelve` y `pickle`
shelve: un almacén clave-valor persistente que se comporta como un diccionario, pero cuyos datos sobreviven al cierre del programa. Ideal para pequeños proyectos que necesitan guardar objetos Python sin montar una base de datos.
import shelve with shelve.open("agenda.db") as db: db["ana"] = {"telefono": "123", "email": "ana@example.com"} print(db["ana"])
[PYTHON] En Python 3.15,shelve(y los módulosdbm.dumbydbm.sqlite3) incorporan un nuevo métodoreorganize()que compacta el archivo en disco eliminando el espacio liberado por entradas borradas. Antes, los archivosshelvepodían crecer indefinidamente aunque se eliminaran datos; ahora puedes llamar adb.reorganize()para recuperar ese espacio. Ten en cuenta que puede aumentar temporalmente el uso de disco y que no todos los backends dedbmlo soportan.
pickle: serializa objetos Python arbitrarios a bytes y viceversa. Es potente, pero inseguro si los datos provienen de una fuente no confiable, porque puede ejecutar código arbitrario al deserializar. Úsalo solo con datos propios o de confianza.
[BUENA PRÁCTICA] Para archivos de configuración y datos compartidos, prefiere JSON (legible, portable, seguro). Para datos internos de la aplicación que no necesitan ser legibles por humanos,pickleoshelvepueden ser adecuados, pero documenta claramente que el archivo no debe abrirse con datos externos.
5.11. Errores de archivos y permisos
Las operaciones con archivos son propensas a errores que debes manejar:
- FileNotFoundError: el archivo no existe (al leer) o la ruta es incorrecta.
- PermissionError: no tienes permisos para leer o escribir en esa ubicación.
- IsADirectoryError: intentas abrir un directorio como si fuera un archivo.
- UnicodeDecodeError: la codificación no coincide con el contenido.
from pathlib import Path import logging def leer_configuracion(ruta): """Lee un archivo de configuración y devuelve su contenido, o None si falla.""" ruta = Path(ruta) try: with open(ruta, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: logging.error("No se encontró el archivo: %s", ruta) return None except PermissionError: logging.error("Sin permisos para leer: %s", ruta) return None except UnicodeDecodeError: logging.error("Codificación no válida en: %s", ruta) return None
[ERROR HABITUAL] CapturarExceptionde forma genérica al abrir archivos. Es mejor capturar los tipos específicos (FileNotFoundError,PermissionError, etc.) para no ocultar errores de programación. Sigue las recomendaciones del Módulo 11.
6. Ejemplos comentados
Ejemplo 1: Leer un archivo de texto y contar líneas.
from pathlib import Path def contar_lineas(ruta): """Devuelve el número de líneas de un archivo de texto.""" ruta = Path(ruta) if not ruta.is_file(): raise FileNotFoundError(f"No existe el archivo: {ruta}") with open(ruta, "r", encoding="utf-8") as f: return sum(1 for _ in f) print(contar_lineas("datos.txt"))
Seguimiento: sum(1 for _ in f) es una expresión generadora que cuenta las líneas sin cargar el archivo en memoria. Path.is_file() comprueba la existencia antes de abrir. La excepción se propaga si el archivo no existe.
Ejemplo 2: Escribir y leer un CSV de contactos.
import csv from pathlib import Path ruta = Path("contactos.csv") # Escribir with open(ruta, "w", newline="", encoding="utf-8") as f: escritor = csv.writer(f) escritor.writerow(["nombre", "telefono"]) escritor.writerow(["Ana", "123456789"]) escritor.writerow(["Luis", "987654321"]) # Leer como diccionarios with open(ruta, newline="", encoding="utf-8") as f: lector = csv.DictReader(f) for contacto in lector: print(f"{contacto['nombre']}: {contacto['telefono']}")
Seguimiento: csv.DictReader usa la primera fila como cabecera y devuelve cada fila como un diccionario. newline="" evita problemas de saltos de línea en Windows.
Ejemplo 3: Guardar y recuperar configuración en JSON.
import json from pathlib import Path RUTA_CONFIG = Path("config.json") def guardar_config(config): """Guarda un diccionario de configuración en JSON.""" with open(RUTA_CONFIG, "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) def cargar_config(): """Carga la configuración desde JSON, o devuelve un diccionario vacío.""" try: with open(RUTA_CONFIG, "r", encoding="utf-8") as f: return json.load(f) except FileNotFoundError: return {} guardar_config({"tema": "oscuro", "idioma": "es"}) print(cargar_config())
Seguimiento: json.dump escribe el diccionario; json.load lo lee. ensure_ascii=False preserva los caracteres acentuados. FileNotFoundError se maneja devolviendo un diccionario vacío, un patrón común para configuraciones opcionales.
7. Seguimiento de la ejecución
Cuando abres un archivo con open(), Python solicita al sistema operativo un descriptor de archivo (un identificador numérico) y crea un objeto de archivo en memoria. Al leer, el sistema operativo transfiere bytes del disco al buffer del programa. Al escribir, los datos van al buffer y se escriben físicamente en el disco cuando el buffer se llena o cuando el archivo se cierra.
El bloque with garantiza que el archivo se cierre al salir, lo que fuerza el vaciado del buffer (flush) y libera el descriptor. Si el programa termina abruptamente sin cerrar el archivo, parte de los datos podría perderse.
Al iterar sobre un archivo (for linea in f), Python lee el archivo en bloques internos y devuelve una línea cada vez, sin cargar todo el contenido en memoria. Esto es lo que permite procesar archivos enormes con un uso de memoria constante.
8. Errores habituales
| Error | Causa | Cómo diagnosticarlo |
|---|---|---|
FileNotFoundError | La ruta no existe o es incorrecta. | Comprueba con Path(ruta).exists(). Usa Path.cwd() para ver el directorio actual. |
PermissionError | Sin permisos de lectura/escritura. | Verifica los permisos del archivo o elige otra ubicación (por ejemplo, Path.home()). |
UnicodeDecodeError | Codificación incorrecta al leer. | Especifica encoding="utf-8" o la codificación correcta. En CSV de Windows, prueba utf-8-sig. |
| Archivo vacío tras escribir | Se abrió en modo "w" y no se escribió nada, o no se cerró el archivo. | Usa with para garantizar el cierre. Revisa que .write() se haya llamado. |
| Líneas en blanco en CSV en Windows | Falta newline="" al abrir. | Añade newline="" en open(). |
TypeError al serializar JSON | El objeto contiene un tipo no serializable (por ejemplo, set). | Convierte el objeto a un tipo serializable (lista, diccionario) antes de json.dump. |
ValueError: I/O operation on closed file | Se usa el archivo fuera del bloque with. | Guarda el contenido en una variable dentro del with y úsalo después. |
[ERROR HABITUAL] El error de “archivo vacío” suele deberse a no cerrar el archivo. Sinwithni.close(), el buffer puede no haberse vaciado en el disco.withes la solución.
9. Particularidades de Python frente a otros lenguajes
- pathlib como abstracción de rutas: en C, las rutas son cadenas y el programador debe gestionar los separadores. En Java, java.nio.file.Path ofrece una abstracción similar. pathlib es la forma idiomática en Python y elimina las diferencias entre Windows y Linux.
- with como gestor de contexto: Python popularizó este patrón para garantizar la limpieza de recursos. En C, se usan goto o limpieza manual; en Java, try-with-resources. with es más legible y difícil de olvidar.
- UTF-8 como predeterminado (Python 3.15): otros lenguajes dependen de la configuración regional del sistema. Python 3.15 unifica el comportamiento entre plataformas, lo que simplifica la portabilidad.
- csv y json en la biblioteca estándar: en C, tendrías que parsear a mano o usar bibliotecas externas. Python incluye ambos módulos, lo que reduce las dependencias.
- shelve como diccionario persistente: es una solución ligera y pythonica para pequeños proyectos, sin SQL ni esquemas.
10. Buenas prácticas
Incorrecto (sin with, sin encoding, ruta manual):
f = open("C:\\datos\\archivo.txt", "w") f.write("Hola") # Si hay una excepción antes de f.close(), el archivo queda abierto y los datos pueden perderse.
Mejor (portable, seguro y explícito):
from pathlib import Path ruta = Path.home() / "datos" / "archivo.txt" ruta.parent.mkdir(parents=True, exist_ok=True) with open(ruta, "w", encoding="utf-8") as f: f.write("Hola\n")
[BUENA PRÁCTICA] Usapathlibpara construir rutas,withpara abrir archivos,encoding="utf-8"explícito, y maneja las excepciones específicas. Itera sobre el archivo en lugar de cargarlo completo si es grande. Y no usespicklecon datos de fuentes no confiables.
11. Windows y Linux
- Rutas: pathlib unifica el separador. Path("datos") / "archivo.txt" produce datos\archivo.txt en Windows y datos/archivo.txt en Linux.
- Codificación: con Python 3.15, UTF-8 es el predeterminado en ambos sistemas. Aun así, especifica encoding="utf-8" para mayor claridad y compatibilidad.
- CSV y newline="": es especialmente importante en Windows, donde el módulo csv puede añadir \r\n adicionales si no se especifica.
- Permisos: en Linux, los permisos de archivo (lectura, escritura, ejecución) son más estrictos. Si obtienes PermissionError, revisa los permisos con ls -l y asegúrate de que el usuario tiene acceso.
- Rutas reservadas en Windows: PurePath.is_reserved() se elimina en Python 3.15. Usa os.path.isreserved() si necesitas comprobar nombres como CON, PRN, AUX, etc.
[WINDOWS / LINUX] Si un script funciona en Linux pero falla en Windows conFileNotFoundError, revisa las mayúsculas y minúsculas: Windows no distingue, Linux sí.Path("Datos.txt")no encontrarádatos.txten Linux.
12. Ejercicios guiados
Ejercicio guiado 1: Leer y mostrar un archivo.
1. Crea un archivo notas.txt con tres líneas de texto.
2. Escribe un programa que lo lea línea a línea y muestre cada línea numerada.
3. Predice: ¿Qué ocurre si el archivo no existe? Añade manejo de FileNotFoundError para mostrar un mensaje claro.
Ejercicio guiado 2: Escribir un CSV de notas.
1. Pide al usuario tres pares de nombre y nota.
2. Guárdalos en un archivo notas.csv con cabecera nombre,nota.
3. Usa csv.writer y newline="".
4. Vuelve a leer el archivo con csv.DictReader y muestra cada fila.
Ejercicio guiado 3: Guardar y cargar JSON.
1. Crea un diccionario con tu nombre, edad e intereses.
2. Guárdalo en perfil.json con json.dump y ensure_ascii=False.
3. Ábrelo con un editor de texto y observa el formato.
4. Carga el JSON con json.load y muestra el diccionario resultante.
13. Ejercicios autónomos
1. Predicción: ¿Qué ocurre si abres un archivo en modo "w" que ya contenía datos, sin escribir nada? ¿Y si lo abres en modo "a"?
2. pathlib: Construye una ruta que apunte a documentos/proyectos/curso/ejercicio.txt dentro del directorio personal del usuario, usando Path.home(). Crea los directorios necesarios con mkdir(parents=True, exist_ok=True) y escribe una línea en el archivo.
3. CSV: Lee un archivo CSV con columnas producto,precio,cantidad y calcula el valor total (precio * cantidad) de cada fila. Muestra el resultado y el total general.
4. JSON: Crea una lista de diccionarios (por ejemplo, contactos) y guárdala en JSON. Luego cárgala, añade un contacto nuevo y vuelve a guardarla.
5. Reto opcional: Investiga el nuevo método reorganize() de shelve en Python 3.15. Escribe un pequeño programa que cree un shelve, inserte diez entradas, elimine cinco y llame a reorganize(). Comprueba el tamaño del archivo antes y después.
14. Soluciones razonadas
Solución al ejercicio guiado 1:
from pathlib import Path def mostrar_archivo(ruta): ruta = Path(ruta) try: with open(ruta, "r", encoding="utf-8") as f: for numero, linea in enumerate(f, start=1): print(f"{numero}: {linea.rstrip()}") except FileNotFoundError: print(f"No se encontró el archivo: {ruta}") mostrar_archivo("notas.txt")
enumerate(f, start=1) numera las líneas desde 1. rstrip() elimina el salto de línea. FileNotFoundError se maneja con un mensaje claro.
Solución al ejercicio guiado 2:
import csv datos = [] for _ in range(3): nombre = input("Nombre: ") nota = float(input("Nota: ")) datos.append({"nombre": nombre, "nota": nota}) with open("notas.csv", "w", newline="", encoding="utf-8") as f: escritor = csv.DictWriter(f, fieldnames=["nombre", "nota"]) escritor.writeheader() escritor.writerows(datos) with open("notas.csv", newline="", encoding="utf-8") as f: for fila in csv.DictReader(f): print(fila)
csv.DictWriter escribe diccionarios usando las claves como columnas. writeheader() escribe la cabecera. writerows() escribe todas las filas de una vez.
Solución al ejercicio 3 (JSON):
import json perfil = { "nombre": "Ana", "edad": 25, "intereses": ["Python", "ajedrez"] } with open("perfil.json", "w", encoding="utf-8") as f: json.dump(perfil, f, ensure_ascii=False, indent=2) with open("perfil.json", "r", encoding="utf-8") as f: perfil_cargado = json.load(f) print(perfil_cargado)
Solución al ejercicio 1 (predicción): Abrir en modo "w" vacía el archivo inmediatamente, aunque no escribas nada. Abrir en modo "a" no vacía el archivo; si no escribes, queda igual.
Solución al reto opcional (orientativa):
import shelve import os with shelve.open("prueba.db") as db: for i in range(10): db[f"clave_{i}"] = i for i in range(5): del db[f"clave_{i}"] tam_antes = os.path.getsize("prueba.db") with shelve.open("prueba.db") as db: db.reorganize() tam_despues = os.path.getsize("prueba.db") print(f"Antes: {tam_antes} bytes, después: {tam_despues} bytes")
El tamaño debería reducirse después de reorganize(), aunque el resultado exacto depende del backend de dbm.
15. Resumen
- La persistencia permite que los datos sobrevivan entre ejecuciones; los archivos son el mecanismo más directo.
- pathlib.Path construye rutas portables entre Windows y Linux; el operador / une segmentos.
- open() abre archivos en modos "r", "w", "a" o "x"; "w" vacía el archivo, "a" añade al final.
- with garantiza el cierre del archivo incluso si hay errores.
- Itera sobre el archivo (for linea in f) para procesar archivos grandes sin cargarlos en memoria.
- Python 3.15 usa UTF-8 por defecto para la entrada/salida de texto, eliminando diferencias entre sistemas. Aun así, especifica encoding="utf-8" explícitamente.
- csv lee y escribe datos tabulares; newline="" es importante en Windows.
- json convierte entre objetos Python y texto JSON; ensure_ascii=False preserva caracteres no ASCII.
- shelve ofrece un diccionario persistente; en Python 3.15 incorpora reorganize() para compactar el archivo.
- Maneja FileNotFoundError, PermissionError y UnicodeDecodeError de forma específica.
16. Autoevaluación
1. ¿Por qué es importante usar with al abrir archivos? ¿Qué ocurre si no lo haces?
2. ¿Qué diferencia hay entre abrir un archivo en modo "w" y en modo "a"?
3. ¿Cómo construirías una ruta portable que apunte a datos/registros.txt dentro del directorio personal del usuario?
4. ¿Qué hace newline="" al trabajar con CSV en Windows y por qué es necesario?
5. Reto: Sin ejecutar, predice qué ocurre si abres un archivo en modo "r" que no existe. ¿Qué excepción se lanza? ¿Cómo lo manejarías?
17. Fuentes oficiales
- Tutorial oficial: Reading and Writing Files: https://docs.python.org/3.15/tutorial/inputoutput.html#reading-and-writing-files
- Biblioteca estándar: pathlib — Object-oriented filesystem paths: https://docs.python.org/3.15/library/pathlib.html
- Biblioteca estándar: csv — CSV File Reading and Writing: https://docs.python.org/3.15/library/csv.html
- Biblioteca estándar: json — JSON encoder and decoder: https://docs.python.org/3.15/library/json.html
- Biblioteca estándar: shelve — Python object persistence: https://docs.python.org/3.15/library/shelve.html
- Novedades de Python 3.15 (UTF-8 por defecto, PEP 686; shelve.reorganize()): https://docs.python.org/3/whatsnew/3.15.html
- PEP 686 – Make UTF-8 mode default: https://peps.python.org/pep-0686/
- PEP 8 – Style Guide for Python Code: https://peps.python.org/pep-0008/
