Avant de faire du code à plein temps, j'étais contrôleur de gestion. Autant vous dire que des fichiers CSV, j'en ai manipulé des centaines. Et comme je commençais à utiliser Python, je me suis vite tourné vers la bibliothèque standard csv et la bibliothèque tierce openpyxl.
Vous l'aurez deviné, dans ce guide, nous allons nous intéresser à la bibliothèque standard csv.
Lire les fichiers CSV
csv.reader : la méthode classique
La façon la plus basique de lire un fichier est d'utiliser csv.reader. Cette fonction traite chaque ligne et la transforme en une liste de chaînes de caractères.
Imaginons un fichier CSV utilisateurs.csv :
nom,age,ville,actif Patrick,45,Bordeaux,true Sebastien,29,Toulouse,false
import csv with open('utilisateurs.csv', 'r', newline='', encoding='utf-8') as fichier: reader = csv.reader(fichier, delimiter=',') for ligne in reader: print(ligne) # Affiche : # ['nom', 'age', 'ville', 'actif'] # ['Patrick', '45', 'Bordeaux', 'true'] # ['Sebastien', '29', 'Toulouse', 'false']
Nous ouvrons notre fichier avec un gestionnaire de contexte with open(). Nous passons le fichier à csv.reader en lui précisant que le delimiter est une virgule, bien que ce soit la valeur par défaut (c'est juste histoire de vous montrer le paramètre 😛).
La fonction nous renvoie un itérateur que l'on assigne à la variable reader, et à chaque tour, la boucle for lit une ligne du fichier et nous la renvoie sous forme de liste.
Et le paramètre newline='' dans tout ça ?
Par défaut, la fonction open() de Python utilise un mécanisme appelé fins de ligne universelles (Universal Newlines). Elle intercepte le texte et traduit automatiquement toutes les fins de ligne (\r\n sous Windows, ou les anciens \r) en un simple \n pour standardiser le texte.
Mais le module csv possède son propre moteur interne pour gérer tous les types de sauts de ligne.
Imaginez ce fichier CSV avec un saut de ligne au milieu d'une cellule :
nom,description "Patrick","Ligne 1 Ligne 2"
Le module csv sait analyser un \n coincé entre deux guillemets. Mais si open() traduit et altère les caractères invisibles (\r\n ou \r) avant de les passer au module csv, on risque d'avoir un problème de lignes mal découpées ou de données corrompues.
newline='' permet de désactiver cette traduction anticipée. Le module csv reçoit les données brutes et fait son travail proprement.
Renvoyer des dictionnaires avec csv.DictReader
Avec csv.reader, vous allez accéder à vos données avec des index. Mais comme csv.DictReader renvoie des dictionnaires en utilisant la première ligne du fichier comme clés, votre script ne risque pas de casser si demain vous ajoutez une colonne au fichier CSV.
Si je reprends le CSV que l'on a cité en tout début d'article et que j'utilise DictReader :
import csv with open('utilisateurs.csv', 'r', newline='', encoding='utf-8') as fichier: reader = csv.DictReader(fichier) for ligne in reader: print(f"{ligne['nom']} a {ligne['age']} ans et habite à {ligne['ville']}") # Patrick a 45 ans et habite à Bordeaux # Sebastien a 29 ans et habite à Toulouse
À noter
Si votre fichier ne possède pas d'en-tête, vous pouvez forcer vos propres clés via le paramètre fieldnames : csv.DictReader(fichier, fieldnames=['nom', 'age', 'ville', 'actif']).
Écrire et exporter des données
La base : csv.writer
Après csv.reader, csv.writer ! Inutile de préciser que c'est pour écrire de manière simple et efficace :
import csv donnees = [ ["nom", "ville"], ["Patrick", "Bordeaux"], ["Sébastien", "Toulouse"] ] with open('export.csv', 'w', newline='', encoding='utf-8') as fichier: writer = csv.writer(fichier, delimiter=',') writer.writerows(donnees)
Vous pouvez aller consulter votre nouveau fichier export.csv.
Structurer son export avec csv.DictWriter
Si vos données proviennent d'une API ou d'une base de données, vous manipulez bien souvent des listes de dictionnaires. On utilisera alors DictWriter en renseignant obligatoirement le paramètre fieldnames pour indiquer à Python dans quel ordre écrire les colonnes.
import csv utilisateurs = [ {"nom": "Patrick", "age": 45, "ville": "Bordeaux", "actif": "true"}, {"nom": "Sébastien", "age": 29, "ville": "Toulouse", "actif": "false"}, # Il manque le statut d'activité pour Ely ! {"nom": "Ely", "age": 28, "ville": "Ons en Bray"} ] colonnes = ["nom", "age", "ville", "actif"] with open('export.csv', 'w', newline='', encoding='utf-8') as fichier: # restval='' indique quoi écrire si une clé est manquante dans le dictionnaire writer = csv.DictWriter(fichier, fieldnames=colonnes, restval='N/A') # On écrit l'en-tête en premier ! writer.writeheader() writer.writerows(utilisateurs)
Et si vous ouvrez le fichier CSV :
nom,age,ville,actif Patrick,45,Bordeaux,true Sébastien,29,Toulouse,false Ely,28,Ons en Bray,N/A
Si le dictionnaire que vous passez à writerow() contient des clés qui ne sont pas dans fieldnames, Python lèvera une ValueError. Pour éviter cela, on peut utiliser le paramètre extrasaction='ignore', sa valeur par défaut étant extrasaction='raise'.
import csv # Imaginons une clé "Hobby" non prévue donnees_api = [{"nom": "Patrick", "age": 45, "ville": "Bordeaux", "actif": "true", "Hobby": "Pétanque"}] colonnes = ["nom", "age", "ville", "actif"] with open('export.csv', 'w', newline='', encoding='utf-8') as fichier: # Le champ inconnu (Hobby) sera ignoré silencieusement writer = csv.DictWriter(fichier, fieldnames=colonnes, extrasaction="ignore") writer.writeheader() writer.writerows(donnees_api)
S'adapter à tous les formats
Les séparateurs
Les paramètres régionaux français utilisent la virgule comme séparateur décimal. Des logiciels comme Excel décident souvent d'exporter les fichiers CSV en utilisant des points-virgules comme séparateurs de colonnes afin d'éviter les conflits.
Si vous n'avez qu'un seul fichier à traiter, le plus simple est de passer le paramètre delimiter à la volée dans la fonction reader :
import csv with open('donnees_fr.csv', 'r', newline='', encoding='utf-8') as fichier: lecteur = csv.reader(fichier, delimiter=';')
Lorsque votre application grandit et que vous devez réutiliser une combinaison de paramètres sur plusieurs fichiers, vous pouvez créer votre propre dialecte avec register_dialect.
À noter
Le dialecte utilisé par défaut est excel. Si vous êtes curieux d'aller fouiller dans le code source de Python, ce dialecte excel est directement défini dans le fichier csv.py de la bibliothèque.
import csv # On enregistre notre dialecte "europeen" une seule fois csv.register_dialect('europeen', delimiter=';') with open('donnees_fr.csv', 'r', newline='', encoding='utf-8') as fichier: # On l'appelle via un simple argument positionnel ! lecteur = csv.reader(fichier, 'europeen')
Pratique ! Si le format attendu change demain, vous n'aurez qu'une seule ligne à modifier 😎.
Détecter le format automatiquement
Si vous développez une application qui permet à vos utilisateurs d'importer leurs propres fichiers CSV, ils peuvent très bien importer un fichier avec des virgules, un autre avec des points-virgules, etc.
csv intègre la classe Sniffer. Drôle de nom, à croire qu'elle est capable de renifler vos fichiers pour identifier leur format !
import csv with open('surprise.csv', 'r', newline='', encoding='utf-8') as fichier: echantillon = fichier.read(1024) renifleur = csv.Sniffer() try: dialecte_devine = renifleur.sniff(echantillon) a_un_en_tete = renifleur.has_header(echantillon) print(f"Séparateur trouvé : {dialecte_devine.delimiter}") print(f"Présence d'un en-tête : {a_un_en_tete}") fichier.seek(0) lecteur = csv.reader(fichier, dialect=dialecte_devine) # Suite du traitement... except csv.Error: print("Impossible de déterminer le format de ce fichier.") # Résultat de l'exécution : # Séparateur trouvé : , # Présence d'un en-tête : True
De cette manière, vous pouvez traiter les données de vos utilisateurs sereinement sans avoir à coder manuellement les vérifications quant au formatage.
Les constantes de Quoting
Il est possible de contrôler la façon dont les guillemets sont placés dans le CSV généré. Le module propose quatre constantes :
csv.QUOTE_MINIMAL: la valeur par défaut, les guillemets sont placés uniquement si nécessaire (si le champ contient le séparateur, un saut de ligne, etc.)
# Donnée : ["Sébastien", 'A dit "Bonjour"'] # Résultat : Sébastien,"A dit ""Bonjour"""
csv.QUOTE_ALL: peu importe le type de données, il y aura des guillemets partout
# Donnée : ["Patrick", 45, "Bordeaux"] # Résultat : "Patrick","45","Bordeaux"
csv.QUOTE_NONE: ne met aucun guillemet. Si un champ contient le séparateur, Python refuse d'écrire pour ne pas corrompre le fichier (à moins de configurer un caractère d'échappement)
# Donnée : ["Patrick", "Bordeaux, France"] # Résultat : _csv.Error: need to escape, but no escapechar set
csv.QUOTE_NONNUMERIC: met des guillemets uniquement autour des chaînes de caractères.Noneest converti en chaîne vide et reçoit aussi des guillemets
# Donnée : ["Patrick", decimal.Decimal('45.5'), True, None] # Résultat : "Patrick",45.5,True,""
À noter
Depuis Python 3.12, csv.QUOTE_STRINGS corrige le cas None : il est écrit sans guillemets et sera relu comme None.
Attention
Lors de la lecture avec cette même constante : QUOTE_NONNUMERIC convertit aveuglément tout ce qui n'a pas de guillemets en float. Donc votre Decimal sera relu en tant que float, True fera planter le programme (float('True')) et None deviendra une chaîne vide.