Skip to content

📙 Clase 19 — Archivos: open() y with

Fase 4 · Archivos y el mundo exterior ⬅️ Volver al índice de clases

🎯 Qué aprendí

  • Leer y escribir archivos de texto con open() y los modos r / w / a.
  • El bloque with: abre, usa y cierra solo (la forma correcta siempre).
  • El primer paso para que tu app recuerde datos entre ejecuciones.

📖 PARTE TEÓRICA

💾 1. El problema que resolvemos

Todo lo que tu app guarda en listas y objetos vive en RAM: al cerrar la ventana, desaparece. Los archivos son la primera forma de persistencia:

        RAM (se pierde al cerrar)         DISCO (persiste)
   self._tareas = [Tarea(...), …]   ──▶   tareas.json / notas.txt / app.db
                                    ◀──   (se recarga al abrir)

📝 2. Escribir: modos w y a

python
# w = write: CREA el archivo (o lo VACÍA si existía) y escribe
with open("notas.txt", "w", encoding="utf-8") as f:
    f.write("primera línea\n")       # \n = salto de línea (no lo pone solo)
    f.write("segunda línea\n")

# a = append: AGREGA al final sin borrar lo que había
with open("notas.txt", "a", encoding="utf-8") as f:
    f.write("tercera línea\n")
ModoQué haceSi el archivo existeSi no existe
"r"leer (default)lo abre💥 FileNotFoundError
"w"escribirlo vacía ⚠️lo crea
"a"agregar al finalconserva y añadelo crea

⚠️ El gotcha del modo w: abrir con "w" borra el contenido al instante, aunque no escribas nada. Para añadir sin destruir: "a".

📌 Pon siempre encoding="utf-8": garantiza que tildes y ñ funcionen igual en macOS, Windows y Linux.

📖 3. Leer: read(), línea a línea, readlines()

python
with open("notas.txt", encoding="utf-8") as f:
    contenido = f.read()             # TODO el archivo en un string
# 'primera línea\nsegunda línea\ntercera línea\n'

with open("notas.txt", encoding="utf-8") as f:
    for linea in f:                  # línea por línea (eficiente p/ archivos grandes)
        print(linea.strip())         # .strip() quita el \n final

with open("notas.txt", encoding="utf-8") as f:
    lineas = f.readlines()           # lista de líneas (con su \n)
# ['primera línea\n', 'segunda línea\n', 'tercera línea\n']

🛡️ 4. Por qué with y no open() a secas

with garantiza que el archivo se cierra al salir del bloque — incluso si algo explota a mitad. Sin él, un archivo abierto queda "colgado" (datos sin volcar, archivo bloqueado).

python
# ❌ Frágil: si algo falla antes del close, queda abierto
f = open("notas.txt")
contenido = f.read()
f.close()

# ✅ Siempre así: cierra SOLO, pase lo que pase
with open("notas.txt", encoding="utf-8") as f:
    contenido = f.read()

🧪 Tip de entrevista: "¿Qué ventaja tiene with open(...)?" → Es un context manager: cierra el archivo automáticamente aunque haya una excepción (equivale a try/finally con close()). En la Fase 5 verás cómo crear los tuyos.

Y si el archivo puede no existir (primer arranque de tu app):

python
try:
    with open("no_existe.txt", encoding="utf-8") as f:
        datos = f.read()
except FileNotFoundError:
    datos = ""        # primer arranque: empezar vacío

🖥️ EN TU APP DE ESCRITORIO

El patrón cargar al abrir / guardar al cambiar — la mejora #1 que pide tu To-Do:

python
class App(ctk.CTk):
    ARCHIVO = "tareas.txt"

    def __init__(self):
        super().__init__()
        self._tareas = self._cargar()        # ① al ABRIR: leer del disco

    def _cargar(self):
        try:
            with open(self.ARCHIVO, encoding="utf-8") as f:
                return [linea.strip() for linea in f if linea.strip()]
        except FileNotFoundError:
            return []                        # primer arranque

    def _guardar(self):                      # ② tras CADA cambio: volcar al disco
        with open(self.ARCHIVO, "w", encoding="utf-8") as f:
            for tarea in self._tareas:
                f.write(tarea + "\n")

    def _agregar(self):
        self._tareas.append(self.entry.get())
        self._guardar()                      # persistir de inmediato
        self._pintar()

💡 ¿Guardar en cada cambio o solo al cerrar? En cada cambio: si la app se cuelga o se va la luz, no pierdes nada. Para listas pequeñas el costo es invisible.


🗄️ CON BASE DE DATOS (caso de uso)

Estás construyendo la escalera de la persistencia — cada peldaño para su tamaño de problema:

.txt (esta clase) ──▶ .json (Clase 20) ──▶ .csv (Clase 21) ──▶ SQLite (Fase 6)
texto plano           estructura            tablas p/ Excel     consultas, filtros,
una línea = un dato   (listas de dicts)                         miles de registros

🗄️ Un archivo .txt guarda strings "planos". En cuanto tu dato tenga campos (texto + hecha + fecha), necesitas JSON — la próxima clase.


🏋️ EJERCICIOS CON SOLUCIÓN

Ejercicio 1 — Diario

Escribe dos entradas en diario.txt (modo w la primera, a la segunda) y luego imprímelo completo.

Ver solución
python
with open("diario.txt", "w", encoding="utf-8") as f:
    f.write("Día 1: aprendí archivos\n")
with open("diario.txt", "a", encoding="utf-8") as f:
    f.write("Día 2: aprendí with\n")
with open("diario.txt", encoding="utf-8") as f:
    print(f.read())
# Día 1: aprendí archivos
# Día 2: aprendí with

Ejercicio 2 — Contar líneas

Escribe contar_lineas(ruta) que devuelva cuántas líneas no vacías tiene un archivo, o 0 si no existe.

Ver solución
python
def contar_lineas(ruta):
    try:
        with open(ruta, encoding="utf-8") as f:
            return sum(1 for linea in f if linea.strip())
    except FileNotFoundError:
        return 0

print(contar_lineas("diario.txt"))    # 2
print(contar_lineas("nada.txt"))      # 0

Ejercicio 3 — El gotcha del modo w

¿Qué queda en datos.txt tras este código y por qué?

python
with open("datos.txt", "w") as f:
    f.write("hola")
with open("datos.txt", "w") as f:
    pass
Ver solución
python
# Queda VACÍO. La segunda apertura en modo "w" borra el contenido al abrir,
# aunque no se escriba nada. Para conservar y añadir: modo "a".

❓ Preguntas y respuestas (autoevaluación)

1. ¿Qué diferencia hay entre los modos w y a?

w vacía el archivo y escribe desde cero; a conserva el contenido y agrega al final.

2. ¿Por qué usar with open(...) siempre?

Cierra el archivo automáticamente, incluso si hay una excepción a mitad del bloque.

3. ¿Qué excepción salta al leer un archivo inexistente y cómo la maneja una app?

FileNotFoundError; se atrapa y se arranca con datos vacíos (primer arranque).

4. ¿Cuándo guardar en disco: en cada cambio o al cerrar la app?

En cada cambio: ante un cierre inesperado no se pierde nada, y el costo es mínimo.


📎 Apuntes relacionados

➡️ Siguiente

Clase 20 · JSON — guardar tu lista de diccionarios tal cual.