Le module csv

Apprenez à utiliser le module csv de Python.

Publié le par Gabriel Trouvé (mis à jour le )

33 minutes

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
BASH
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']
PYTHON

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"
SHELL

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
PYTHON

À 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)
PYTHON

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)
PYTHON

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
SHELL

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)
PYTHON

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=';')
PYTHON

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')
PYTHON

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
PYTHON

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"""
SHELL
  • 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"
PYTHON
  • 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
SHELL
  • csv.QUOTE_NONNUMERIC : met des guillemets uniquement autour des chaînes de caractères. None est 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,""
SHELL

À 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.

Bravo, tu es prêt à passer à la suite

Rechercher sur le site

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.