Lectura, modificación y escritura de archivos

Modos de apertura: lectura, escritura, añadido

open(ruta, modo) acepta un segundo argumento que dice qué vas a hacer con el archivo — el mismo concepto que > y >> de la terminal (módulo 5), ahora dentro de Python:

Modo Significa Equivalente en shell
"r" (por defecto) leer
"w" escribir, sobrescribiendo si ya existe >
"a" añadir al final, sin borrar lo que había >>
with open("species_observations.csv") as f:
    contenido = f.read()      # todo el archivo como una sola cadena
print(contenido[:50])         # primeros 50 caracteres

f.read() trae el archivo entero de una vez, como cadena; f.readlines() (módulo 7) lo trae como una lista de líneas; iterar con for linea in f: los lee uno a uno sin cargar el archivo entero en memoria — la opción más segura para archivos grandes.

Escribir archivos con .write()

with open("species_observations.csv") as f:
    lineas = f.readlines()

with open("resumen.txt", "w") as f:
    for linea in lineas[1:]:                 # nos saltamos la cabecera
        campos = linea.strip().split(",")
        sample_id, especie, count = campos[0], campos[5], campos[6]
        f.write(f"{sample_id}: {especie} ({count} individuos)\n")

.write(), a diferencia de print(), no añade un salto de línea automáticamente — por eso el \n al final de la f-string es necesario; sin él, todas las líneas quedarían pegadas en una sola.

Para añadir algo más sin borrar lo ya escrito, abre en modo "a":

with open("resumen.txt", "a") as f:
    f.write(f"Total de observaciones: {len(lineas) - 1}\n")

Un problema real: por qué split(",") no basta

Ya usas .split(",") desde el módulo 8 para trocear una línea CSV. Pruébalo sobre muestras_con_comas.csv:

with open("muestras_con_comas.csv") as f:
    for linea in f:
        campos = linea.strip().split(",")
        print(len(campos), campos)
3 ['sample_id', 'especie', 'notas']
4 ['M01', 'Quercus robur', '"Ejemplar sano', ' sin danos visibles"']
4 ['M02', 'Pinus sylvestris', '"Corteza danada', ' posible plaga"']
3 ['M03', 'Fragaria vesca', '"Sin observaciones"']

Las filas M01 y M02 dan 4 campos en vez de 3: split(",") no sabe que la coma de "Ejemplar sano, sin danos visibles" está protegida por las comillas, y la interpreta como un separador más. El resultado es una lista con el campo de notas partido por la mitad — un error silencioso que desplazaría todos los datos si intentaras acceder a campos[2] esperando la nota completa.

La solución es no reinventar el analizador de CSV: Python trae uno en la librería estándar, el módulo csv, que sí entiende las comillas:

import csv

with open("muestras_con_comas.csv") as f:
    lector = csv.reader(f)
    for fila in lector:
        print(len(fila), fila)
3 ['sample_id', 'especie', 'notas']
3 ['M01', 'Quercus robur', 'Ejemplar sano, sin danos visibles']
3 ['M02', 'Pinus sylvestris', 'Corteza danada, posible plaga']
3 ['M03', 'Fragaria vesca', 'Sin observaciones']

Ahora sí: siempre 3 campos, con la coma de la nota intacta dentro del campo, sin comillas de sobra.

Leer y escribir filas como diccionarios, a mano

csv.reader te da cada fila como una lista (accedes por posición, como con .split(",")). Para trabajar con cada fila como un diccionario — conectando con lo que aprendiste en el módulo 8 — no hace falta ninguna herramienta nueva: basta con trocear la cabecera igual que cualquier otra línea, y emparejar cada nombre de columna con su valor:

with open("species_observations.csv") as f:
    lineas = f.readlines()

cabecera = lineas[0].strip().split(",")

for linea in lineas[1:]:
    valores = linea.strip().split(",")
    fila = dict(zip(cabecera, valores))
    print(fila["species"])

zip(cabecera, valores) empareja cada nombre de columna ("species", "count"…) con el valor de esa misma posición en la fila; dict(...) convierte esos pares en un diccionario. Ten en cuenta que, al construirlo con .split(",") en vez de csv.reader, vuelves a estar expuesto al problema de las comas dentro de las notas que viste más arriba — aquí funciona porque species_observations.csv no tiene comas protegidas por comillas.

Para escribir diccionarios de vuelta a un CSV, el proceso es el inverso: unes los valores con ",".join(...), en el mismo orden que la cabecera.

with open("species_observations.csv") as f:
    lineas = f.readlines()

cabecera = lineas[0].strip().split(",")
abundantes = []
for linea in lineas[1:]:
    valores = linea.strip().split(",")
    fila = dict(zip(cabecera, valores))
    if int(fila["count"]) > 3:
        abundantes.append(fila)

with open("solo_abundantes.csv", "w") as f:
    f.write(",".join(cabecera) + "\n")
    for fila in abundantes:
        f.write(",".join(fila[campo] for campo in cabecera) + "\n")

int(fila["count"]) sigue siendo imprescindible: como viste en el módulo 8, todo lo que sale de un CSV es texto, y "12" > 3 daría un error en Python (no se puede comparar texto con número). Al escribir, fila[campo] for campo in cabecera recorre las columnas en el mismo orden que la cabecera, así que cada valor cae en su columna correcta aunque el diccionario en sí no garantice ningún orden.

Ejercicios

  1. Abre species_observations.csv en modo lectura y usa .read() para imprimir todo su contenido de una vez.
  2. Usa .readlines() y un bucle for para imprimir solo el sample_id (primera columna) de cada fila, saltándote la cabecera.
  3. Escribe un archivo nuevo resumen.txt con una línea por observación, con el formato "<sample_id>: <especie> (<count> individuos)".
  4. Añade (modo "a") una línea final a resumen.txt con el total de observaciones.
  5. Sobre muestras_con_comas.csv, trocea cada línea con .split(",") e imprime cuántos campos obtienes por fila. Comprueba que las filas con coma dentro de las notas dan 4 campos en vez de 3.
  6. Repite el ejercicio anterior con csv.reader y comprueba que ahora todas las filas dan exactamente 3 campos.
  7. Sin usar el módulo csv, abre species_observations.csv, trocea la cabecera y cada fila con .split(","), construye cada fila como diccionario con dict(zip(cabecera, valores)) e imprime fila["species"].
  8. (Reto) A partir del ejercicio anterior, construye una lista abundantes con las filas donde count sea mayor que 3, y escribe solo_abundantes.csv a mano, uniendo cada fila con ",".join(...) en el mismo orden que la cabecera.

Soluciones

  1. with open("species_observations.csv") as f:
        print(f.read())
  2. with open("species_observations.csv") as f:
        lineas = f.readlines()
    for linea in lineas[1:]:
        print(linea.strip().split(",")[0])
  3. Ver la sección “Escribir archivos con .write()”.
  4. Ver la sección “Escribir archivos con .write()” (bloque con modo "a").
  5. with open("muestras_con_comas.csv") as f:
        for linea in f:
            campos = linea.strip().split(",")
            print(len(campos), campos)
  6. import csv
    with open("muestras_con_comas.csv") as f:
        lector = csv.reader(f)
        for fila in lector:
            print(len(fila), fila)
  7. with open("species_observations.csv") as f:
        lineas = f.readlines()
    
    cabecera = lineas[0].strip().split(",")
    for linea in lineas[1:]:
        valores = linea.strip().split(",")
        fila = dict(zip(cabecera, valores))
        print(fila["species"])
  8. with open("species_observations.csv") as f:
        lineas = f.readlines()
    
    cabecera = lineas[0].strip().split(",")
    abundantes = []
    for linea in lineas[1:]:
        valores = linea.strip().split(",")
        fila = dict(zip(cabecera, valores))
        if int(fila["count"]) > 3:
            abundantes.append(fila)
    
    with open("solo_abundantes.csv", "w") as f:
        f.write(",".join(cabecera) + "\n")
        for fila in abundantes:
            f.write(",".join(fila[campo] for campo in cabecera) + "\n")