Convertir una lista de clientes en memoria a un almacenamiento persistente es clave para crear herramientas útiles. Aquí se explica, paso a paso, cómo usar el módulo de csv, la función global open y los context managers de Python para leer y escribir una tabla de clientes en un archivo CSV, además de automatizar el flujo de carga y guardado.
¿Cómo persistir datos en Python con CSV y open?
La transición va de un string “didáctico” a datos tabulares en un archivo CSV. Con open(path, mode) abrimos un archivo, y con csv lo representamos como filas y columnas. Al inicio del programa se cargan los clientes desde disco y, al finalizar, se guardan de vuelta. Así, la información deja de perderse entre ejecuciones.
¿Qué es open y cómo usarlo con context managers?
open recibe un path y regresa un objeto archivo.
Es indispensable cerrarlo para que se escriba a disco y no dejar datos en memoria.
Con context managers usando la palabra clave with se garantiza el cierre automático al salir del bloque.
Ejemplo básico:
withopen('.clients.csv', mode='r')as f: data = f.read()# al salir del with, el archivo se cierra de forma segura
¿Qué modos de archivo y por qué importan?
Modos más comunes: 'r' (read) y 'w' (write).
Existen modos binarios además de lectura y escritura.
Se indican con el parámetro opcional mode de open.
¿Cómo leer y escribir con DictReader y DictWriter?
El módulo de csv permite dos enfoques: como listas (filas indexadas por posición) o como diccionarios mediante csv.DictReader, que usa nombres de campos. Para este caso, se define un esquema con llaves: nombre, compañía, email y posición. La tabla se guarda en un archivo llamado .clients.csv (se menciona el prefijo con punto tal como se describe). También se usa os para eliminar y renombrar archivos al guardar con seguridad usando un archivo temporal .tmp.
¿Cómo inicializar clientes desde el almacenamiento?
Se define la tabla y el esquema de campos.
Se usa csv.DictReader con fieldnames para construir diccionarios por fila.
Se llena la lista global de clientes al iniciar el programa.
if __name__ =='__main__': initialize_clients_from_storage()# aquí van los comandos: create, list, update, delete, search save_clients_to_storage()
¿Cómo se valida el flujo con create, list, update, delete y search?
El ciclo práctico muestra cómo el programa ya “recuerda” la información:
arranca vacío, se ejecuta el comando de listado y no hay clientes.
se usa create para registrar a “David Aroesti” en la compañía Platzi, email “david@david.com”, posición “teacher”.
se vuelve a listar y el cliente aparece.
se prueba update con el UID 0 y se cambia el nombre a “José David Aroesti”; el resto se mantiene.
se ejecuta delete con el UID 0 y la lista queda vacía.
se agrega “Tom” (Platzi, “tom@tom.com”, “ingeniero”) y luego search confirma si “Tom” existe; buscar “James” indica que no está.
¿Qué habilidades y keywords practica este ejercicio?
Persistencia de datos con CSV mediante el módulo de csv.
Manejo de archivos con open: mode='r' y mode='w'.
Uso de context managers con with para cierre automático.
Lectura con csv.DictReader y escritura con csv.DictWriter.
Definición de fieldnames según un schema: name, company, email, position.
Flujo de inicio/fin: initialize al arrancar, save al terminar.
Escritura segura con archivo temporal .tmp, os.remove y os.rename.
Trabajo con datos tabulares: analogía con Excel, Spreadsheets, MySQL y Postgres.
¿Te funcionó con una solución distinta o con otros nombres de campos? Comparte cómo lo adaptaste y, si lo sabes, explica por qué se usa un nombre de archivo con punto inicial para enriquecer el intercambio.
Los archivos con punto inicial, hacen referencia a un archivo oculto.
Buen aporte, hay muchos nuevos en el mundo de la tecnologia haciendo el curso y tal vez no sepan eso
¡Gracias Andres!
No sé cómo al profe le funcionó jejeje, pero en el método _save_clients_to_storage() tuve que sacar el rename del statement del with open… me quedó así para que me funcionará:
En la documentación de Python se explica que, en S.O. Windows, la función remove del módulo os lanzará una excepción si se intenta eliminar un archivo abierto. De manera similar, la función rename lanzará una excepción si se intenta renombrar un archivo cuando el nombre del archivo está en uso.
Muy bien, muchas gracias
✨ Que excelente, gracias por el aporte. ¡Saludos! ✨
En windows hay que cerrar el archivo o te saldra error de tmp
le hice una modificación a la función _initialize_clients_from_storage() para que cree el archivo por si el mismo si no existe.
gracias a este articulo
A mi me daba error en el método _save_clients_to_storage() de que no se podia cambiar el nombre al archivo porque estaba siendo utilizado por otra persona. he añadido una linea a ese método y ya funciona
Este es el metodo con el cambio hecho:
def _save_clients_to_storage():"""Primero lo guardamos en una tabla temporal porque no se puede
reescribir un arhivo que ya está abierto"""
tmp_table_name ='{}.tmp'.format(CLIENT_TABLE)withopen(tmp_table_name, mode='w')asf: writer = csv.DictWriter(f, fieldnames=CLIENT_SCHEMA) writer.writerows(clients) ## asi escribimos varias filas
f.close() os.remove(CLIENT_TABLE) ## eliminamos la tabla original
os.rename(tmp_table_name,CLIENT_TABLE) ## renombramos la tabla temporal con el mismo nombre que tenia la tabla original
Buen aporte
que bien este aporte
Para los que estan en windows y les arroja este error:
FileNotFoundError:[Errno2]No such file or directory:'.clients.csv'
Puedes ponerlo solo como './clients.csv' o 'clients.csv'
DictReader
Esta crea un objeto el cuál mapea la información leída a un diccionario cuyas llaves están dadas por el parámetro fieldnames. Este parámetro es opcional, pero cuando no se especifica en el archivo, la primera fila de datos se vuelve las llaves del diccionario.
DictWriter
Esta clase es similar a la clase DictWriter y hace lo contrario, que es escribir datos a un archivo CSV. La clase es definida como csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)
El parámetro fieldnames define la secuencia de llaves que identifican el orden en el cuál los valores en el diccionario son escritos al archivo CSV. A diferencia de DictReader, esta llave no es opcional y debe ser definida para evitar errores cuando se escribe a un CSV.
fuente: https://code.tutsplus.com/es/tutorials/how-to-read-and-write-csv-files-in-python--cms-29907
mvp del curso
Si le da un error similar a este:
Traceback(most recent call last):File"main.py", line 173,in<module>_save_clients_to_storage()File"main.py", line 26,in _save_clients_to_storage
os.rename(tmp_table_name,CLIENT_TABLE)PermissionError:[WinError32]El proceso no tiene acceso al archivo porque está siendo utilizado por otro proceso:'clients.csv.tmp'->'clients.csv'
Es porque dentro de la función _save_clients_to_storage(), deben de indicarle al programa que cierre el archivo f para que pueda ser guardado el archivo temporal con el mismo nombre .clients.csv. Les comparto el pequeño cambio a realizar:
El . inicial en el archivo se usa para hacer de el archivo algo oculto.
Es como el _ para declarar una variable privada
¿Es una convención o realmente en el sistema de archivos estará oculto?
En el sistema realmente se encuentra oculto, para poder verlo puedes (en Windows) irte al Panel de Control, luego a Apariencia y Personalización, después en la sección "Opciones del Explorador de archivos" das click en la opción "Mostrar todos los archivos y carpetas ocultos", y finalmente quitas la opción "Ocultar las extensiones de archivos..."
Buenas tardes, a mi me funcionó con el siguiente código, tuve que hacer algunas correcciones al código expuesto por el profe.
import csv
import os
CLIENT_TABLE='.clients.csv'CLIENT_SCHEMA=['name','company','email','position']clients =[]def _initialize_clients_from_storage():withopen(CLIENT_TABLE,mode='r')asf: reader = csv.DictReader(f,fieldnames=CLIENT_SCHEMA)for row inreader: clients.append(row)def _save_clients_to_storage(): tmp_table_name ='{}.tmp'.format(CLIENT_TABLE)withopen(tmp_table_name,mode='w')asf: writer = csv.DictWriter(f,fieldnames=CLIENT_SCHEMA) writer.writerows(clients) os.remove(CLIENT_TABLE) os.rename(tmp_table_name,CLIENT_TABLE)#print(CLIENT_TABLE)def create_client(client): global clients
if client not inclients: clients.append(client)else:print('Client already is in the client\'s list')def list_clients():for idx,client inenumerate(clients):print('{uid} | {name} | {company} | {email} | {position}'.format(uid=idx,name=client['name'],company=client['company'],email = client['email'],position = client['position']))#print('{}:{}'.format(idx,client['name']))def delete_client(client_name):
global clients
if client_name inclients: clients.remove(client_name)else:print('Client is not in clients list')def update_client(client_name,update_client_name): global clients
if client_name inclients: index = clients.index(client_mame) clients[index]=updated_name
else:print('Client is not in clients list')def search_client(client_name):for client inclients:if client!=client_name:continueelse:returnTruedef list_client():print(clients)def _print_welcome():print('*'*50)print('WELCOME TO PLATZI VENTAS')print('*'*50)print('what would you like to do today?')print('[C]reate client')print('[L]ist client')print('[S]earch client')print('[U]pdate client')print('[D]elete client')def _get_client_field(field_name): field =Nonewhile not field: field =input('What is the client {}?'.format(field_name))return field
def _get_client_name():returninput('what is the client name?')if __name__ =='__main__':_initialize_clients_from_storage()_print_welcome() command=input() command= command.upper()if command =='C': client ={'name':_get_client_field('name'),'company':_get_client_field('company'),'email':_get_client_field('email'),'position':_get_client_field('position'),} #client_name=_get_client_name()create_client(client) elif command =='D': client_name =_get_client_name()delete_client(client_name)
pass
elif command =='L':list_clients() elif command =='S': client_name=_get_client_name() found =search_client(client_name)if(found):print('The client is in the client\'s list')else:print('Then client is not in our client\'s list'.format(client_name))search_client(client_name) elif command =='U': client_name=_get_client_name() update_client_name=input('What is the updated client name?')update_client(client_name,update_client_name)
pass
else:print('Invalid command')_save_clients_to_storage()
La función "_save_clientes_to_storage" genera un error.
Lo resolví poniendo las líneas de os.remove y os.rename fuera del bloque with
Tiene sentido.
Al mover esas instrucciones remove y rename fuera del context manager, este logra cerrar el archivo y liberarlo del proceso de escritura que lo tenía ocupado.
Muy buena esa. Gracias.
Muchas gracias por hacerme ver ese detalle
Yo como un loco pensando que el CSV tenia que devolverme los títulos y avías agregado un código sin haberlo dicho
Me puse a ver el video varias veces a ver dónde falle
print('uid | name | company | email | position ')print('*'*50)
También descubrí que tienes un error que nunca comentaste ya que te falto la “e” en “fieldnames”
Si quieren conocer más modos de lectura de archivos pueden miran en la documentación oficial de Python
Para que les funcione en Windows, el nombre del archivo debe tener antes un back slash
CLIENT_TABLE='.\clients.csv'
Si alguien me puede ayudar con la siguiente error por favor:
Asi tengo el código , donde me esta marcando el error:
El problema se encuentra en la linea 12, debes colocar CLIENTS_SCHEMA sin [ ], asi como tienes en la linea 21.
Muchas gracias compañero, no me fije en ese pequeño detalle.
hola compañeros
aparte de la extensión .csv hay alguna otra que me pueda servir??
Puedes manejar casi cualquier tipo de archivos con Python. Como archivos xml o lxss, pero lo más usado y lo más fácil es csv
Prefiero utilizar archivos .csv por su esbeltez, pero como dice iKenshu puedes utilizar diversos ficheros, puedes guardar data en .json, .yaml, .xml, .lxss, .xls. En particular para escribir reportes a excel utilizo esta librería https://xlsxwriter.readthedocs.io/ aunque te recomiendo pasar por el curso de Pandas con terminar este curso tendras los conocimientos suficientes
Me parece chido que platzi libere contenidos en youtube, debe ser mucha gente la beneficiada. Pero Me parece poco amable para los suscriptores que solo tengas la opcion de reproducir ese contenido embebido en youtube. Sobre todo cuando tu metodo de estudio ya se empezo a apoyar en los "marcadores". Yo entiendo que asi se disparan las visualizaciones en youtube, pero me siento usado y sin opcion. ¿como lo solucionarian, dando la opcion a reproducirlo en su player con marcadores o liberando el "modulo" de toma de marcadores para que puedas usarlo "manual" en clases con contenido en youtube?
Hola Luis, como estás. Suele suceder que en varios cursos las clases de YouTube tienen temas que ya son explicados en otras clases internas, por lo que las primeras suelen estar diseñadas para un público que no tiene un contexto del curso. Este no es el caso, por lo que voy a planteárselo al equipo de Platzi 😉