Módulo 1 · Lección 47 de 59
Ficheros CSV
Leer y escribir ficheros de hoja de cálculo con el módulo csv, y por qué partir por comas a mano nunca funciona del todo.
Un CSV es un fichero de texto donde cada línea es una fila y los campos van separados por comas. Las siglas son de comma-separated values, y es el formato en el que cualquier programa del mundo —Excel, un banco, una tienda online, una base de datos— te deja exportar una tabla. Si vas a trabajar con datos que no has generado tú, vas a recibir CSV.
Se ve tan sencillo que la primera reacción es partir cada línea con split(","). Funciona con el fichero de ejemplo, falla con el de verdad.
Por qué no basta con split#
Este es un CSV realista, con nombres de producto como los que salen de una tienda:
producto,unidades,precio
Teclado mecánico,12,89.90
"Ratón inalámbrico, pack de 2",30,24.50
"Monitor 27"" curvo",8,219.00
Alfombrilla grande,45,9.95Hay dos cosas raras ahí, y las dos son CSV perfectamente válido. El segundo producto lleva una coma dentro del nombre, por eso va entre comillas. El tercero lleva unas comillas dentro (las pulgadas), y la forma de escribir una comilla dentro de un campo entrecomillado es duplicarla.
Mira qué pasa al partir por comas:
with open("ventas.csv", encoding="utf-8") as fichero:
for linea in fichero:
campos = linea.strip().split(",")
print(len(campos), campos)3 ['producto', 'unidades', 'precio']
3 ['Teclado mecánico', '12', '89.90']
4 ['"Ratón inalámbrico', ' pack de 2"', '30', '24.50']
3 ['"Monitor 27"" curvo"', '8', '219.00']
3 ['Alfombrilla grande', '45', '9.95']La tercera fila se ha partido en cuatro trozos en vez de tres, y ahora campos[1] no es el número de unidades sino un pedazo del nombre. La cuarta conserva las comillas de más. Y falta el tercer caso, el peor: un campo puede contener un salto de línea dentro de las comillas, y entonces una fila lógica ocupa dos líneas del fichero y el bucle for linea in fichero las trata como dos filas.
Nada de esto se arregla con parches. Se arregla usando el módulo que ya viene hecho.
Leer con csv.reader#
import csv
with open("ventas.csv", encoding="utf-8", newline="") as fichero:
lector = csv.reader(fichero)
for fila in lector:
print(len(fila), fila)3 ['producto', 'unidades', 'precio']
3 ['Teclado mecánico', '12', '89.90']
3 ['Ratón inalámbrico, pack de 2', '30', '24.50']
3 ['Monitor 27" curvo', '8', '219.00']
3 ['Alfombrilla grande', '45', '9.95']Tres campos en todas las filas, las comillas quitadas, la coma interna respetada y las pulgadas donde debían estar. csv.reader envuelve el fichero abierto y te va entregando cada fila como una lista de cadenas.
La cabecera es una fila más, así que hay que apartarla:
import csv
with open("ventas.csv", encoding="utf-8", newline="") as fichero:
filas = list(csv.reader(fichero))
cabecera = filas[0]
datos = filas[1:]
print(cabecera)
for fila in datos:
print(f"{fila[0]}: {fila[1]} unidades")['producto', 'unidades', 'precio']
Teclado mecánico: 12 unidades
Ratón inalámbrico, pack de 2: 30 unidades
Monitor 27" curvo: 8 unidades
Alfombrilla grande: 45 unidadesLeer con csv.DictReader, que es lo cómodo#
Trabajar con fila[1] obliga a recordar qué columna es cada número, y el día que alguien añada una columna al principio se rompe todo en silencio. DictReader usa la primera fila como nombres y te entrega cada fila como un diccionario:
import csv
with open("ventas.csv", encoding="utf-8", newline="") as fichero:
lector = csv.DictReader(fichero)
for fila in lector:
print(fila){'producto': 'Teclado mecánico', 'unidades': '12', 'precio': '89.90'}
{'producto': 'Ratón inalámbrico, pack de 2', 'unidades': '30', 'precio': '24.50'}
{'producto': 'Monitor 27" curvo', 'unidades': '8', 'precio': '219.00'}
{'producto': 'Alfombrilla grande', 'unidades': '45', 'precio': '9.95'}La cabecera ya no aparece como fila: se ha convertido en las claves. Y el código que la usa se explica solo:
import csv
with open("ventas.csv", encoding="utf-8", newline="") as fichero:
for fila in csv.DictReader(fichero):
total = int(fila["unidades"]) * float(fila["precio"])
print(f"{fila['producto']:<28} {total:>8.2f} €")Teclado mecánico 1078.80 €
Ratón inalámbrico, pack de 2 735.00 €
Monitor 27" curvo 1752.00 €
Alfombrilla grande 447.75 €Escribir con csv.writer#
import csv
filas = [
["nombre", "departamento", "extension"],
["Lucía Ferrer", "Ventas", "213"],
["Mateo Ruiz", "Soporte técnico", "104"],
["Noa Oliver", "Administración, RRHH", "310"],
]
with open("agenda.csv", "w", encoding="utf-8", newline="") as fichero:
escritor = csv.writer(fichero)
escritor.writerows(filas)nombre,departamento,extension
Lucía Ferrer,Ventas,213
Mateo Ruiz,Soporte técnico,104
Noa Oliver,"Administración, RRHH",310El módulo ha puesto comillas él solo en el único campo que las necesitaba, el que llevaba una coma. Eso es exactamente lo que no consigues concatenando cadenas a mano.
writerows() escribe una lista de filas; writerow(), en singular, escribe una sola, que es lo que usarás dentro de un bucle.
Escribir con csv.DictWriter#
Si tus datos son diccionarios, lo natural es DictWriter. Necesita saber de antemano el orden de las columnas, y la cabecera la escribe él con writeheader():
import csv
empleados = [
{"nombre": "Lucía Ferrer", "departamento": "Ventas", "extension": "213"},
{"nombre": "Mateo Ruiz", "departamento": "Soporte técnico", "extension": "104"},
]
with open("agenda.csv", "w", encoding="utf-8", newline="") as fichero:
campos = ["nombre", "departamento", "extension"]
escritor = csv.DictWriter(fichero, fieldnames=campos)
escritor.writeheader()
escritor.writerows(empleados)nombre,departamento,extension
Lucía Ferrer,Ventas,213
Mateo Ruiz,Soporte técnico,104Si se te olvida writeheader(), el fichero sale sin la primera fila y quien lo lea después con DictReader tomará al primer empleado como nombres de columna.
El punto y coma de Excel en español#
Abre un CSV con comas en un Excel configurado en español y verás toda la fila metida en la primera celda. El motivo es que en España el separador decimal es la coma (89,90), así que Excel no puede usarla también para separar campos y usa el punto y coma.
Se le indica al módulo con delimiter:
import csv
with open("ventas-excel.csv", "w", encoding="utf-8", newline="") as fichero:
escritor = csv.writer(fichero, delimiter=";")
escritor.writerow(["producto", "unidades", "precio"])
escritor.writerow(["Teclado mecánico", 12, "89,90"])producto;unidades;precio
Teclado mecánico;12;89,90El mismo parámetro vale para leer: csv.reader(fichero, delimiter=";") y csv.DictReader(fichero, delimiter=";"). Cuando recibas un CSV ajeno, ábrelo primero con un editor de texto y mira con qué está separado antes de escribir nada.
| Parámetro | Para qué | Valor habitual |
|---|---|---|
delimiter | Carácter que separa campos | "," o ";" |
quotechar | Carácter que entrecomilla | '"' |
fieldnames | Nombres de columna en DictWriter | Una lista de cadenas |
Un informe completo#
Leer las ventas, calcular el importe de cada línea, ordenar y escribir el resultado en otro CSV:
import csv
from pathlib import Path
BASE = Path(__file__).parent
ENTRADA = BASE / "ventas.csv"
SALIDA = BASE / "informe-ventas.csv"
def leer_ventas(ruta: Path) -> list[dict]:
"""Lee el CSV de ventas y devuelve las filas con los números ya convertidos."""
ventas = []
with open(ruta, encoding="utf-8", newline="") as fichero:
for fila in csv.DictReader(fichero):
ventas.append({
"producto": fila["producto"],
"unidades": int(fila["unidades"]),
"precio": float(fila["precio"]),
})
return ventas
def calcular_totales(ventas: list[dict]) -> list[dict]:
"""Añade a cada venta su importe total."""
for venta in ventas:
venta["total"] = round(venta["unidades"] * venta["precio"], 2)
return ventas
def escribir_informe(ruta: Path, ventas: list[dict]) -> None:
"""Vuelca el informe ordenado de mayor a menor importe."""
campos = ["producto", "unidades", "precio", "total"]
ordenadas = sorted(ventas, key=lambda venta: venta["total"], reverse=True)
with open(ruta, "w", encoding="utf-8", newline="") as fichero:
escritor = csv.DictWriter(fichero, fieldnames=campos)
escritor.writeheader()
escritor.writerows(ordenadas)
ventas = calcular_totales(leer_ventas(ENTRADA))
escribir_informe(SALIDA, ventas)
facturacion = sum([venta["total"] for venta in ventas])
print(f"Productos procesados: {len(ventas)}")
print(f"Facturación total: {facturacion:.2f} €")
print(f"Informe escrito en {SALIDA.name}")Productos procesados: 4
Facturación total: 4013.55 €
Informe escrito en informe-ventas.csvY el fichero que deja:
producto,unidades,precio,total
"Monitor 27"" curvo",8,219.0,1752.0
Teclado mecánico,12,89.9,1078.8
"Ratón inalámbrico, pack de 2",30,24.5,735.0
Alfombrilla grande,45,9.95,447.75Fíjate en que los precios han perdido el cero final: 89.90 se ha escrito como 89.9. Es normal, porque al convertirlos con float() dejaron de ser texto y 89.90 y 89.9 son el mismo número. Si necesitas conservar el formato exacto en el fichero de salida, escribe la cadena ya formateada con f"{venta['precio']:.2f}".
El reparto en tres funciones —leer, calcular, escribir— no es adorno. Cada una se puede probar por separado, y el día que los datos lleguen de una base de datos en vez de un CSV solo cambias la primera.