Il peut être nécessaire de lire ou d'écrire des fichiers stockés sur l'ordinateur exécutant vos scripts. Consigner des données dans des fichiers permet de simplifier un programme en externalisant les données et peut être un moyen de s'interfacer avec d'autres programmes et systèmes ainsi qu'avec les utilisateurs. Nous utiliserons la fonction fournie par défaut open(). Avant tout, il est nécessaire de voir comment naviguer dans l'arborescence.

Navigation dans l'arborescence

En fonction du répertoire dans lequel est exécuté votre script, il peut être nécessaire de changer de répertoire de travail du script. Pour se faire, nous utiliserons la fonction chdir(repertoire) dans le module os pour changer de répertoire de travail. Nous utiliserons également la fonction getcwd() du même module. Il est possible de créer un dossier avec mkdir(chemin) :

>>> from os import getcwd, chdir, mkdir
>>> print(getcwd())
/home/antoine
>>> chdir('essais')
>>> print(getcwd())
/home/antoine/essais
>>> mkdir('test')

Ouvrir un fichier

Pour lire un fichier, il faut tout d'abord ouvrir un flux de lecture ou d'écriture de fichier avec la fonction open(fichier,mode (par défaut : 'rt')) avec fichier l'adresse du fichier à ouvrir et mode, le type de flux à ouvrir. Le mode est composé de deux lettres, les droits d'accès (rwxa) et l'encodage (bf). Voici le tableau détaillant les modes de flux de fichier.

CaractèreAction
'r'Ouvrir en lecture seule (défaut)
'w'Ouvrir en écriture. Écrase le fichier existant.
'x'Ouvrir en écriture si et seulement si le fichier n'existe pas déjà.
'a'Ouvrir en écriture. Ajoute au fichier existant.
'b'Mode binaire.
't'Mode texte (défaut).

Pour fermer le flux de fichier avec la méthode close() sur la variable représentant le flux.

Il est important de fermer le flux une fois les opérations sur le fichier terminé.

Lire un fichier

Une fois le flux en lecture ouvert, on peut utiliser les méthodes read() qui retournent une chaîne de caractères contenant l'intégralité du fichier ou readlines() retournant une liste où chaque élément est une ligne du fichier.
>>> fichier = open("texte.txt",'rt')
>>> texte = fichier.read()
>>> print(texte)
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque gravida erat ut lectus convallis auctor.
Fusce mollis sem id tellus auctor hendrerit.
>>> lignes = fichier.readlines()
>>> print(lignes)
['Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n', 'Pellentesque gravida erat ut lectus convallis auctor.\n', 'Fusce mollis sem id tellus auctor hendrerit.\n']
>>> fichier.close()

Chaque ligne est terminée par "\n" qui représente un retour à la ligne. Il s'agit d'un caractère spécial. Si vous écrivez ce caractère dans une chaîne de caractères, Python produira un retour à la ligne :

>>> texte = "Première ligne\nDeuxième ligne"
>>> print(texte)
Première ligne
Deuxième ligne

Écrire un fichier

On peut écrire un fichier si le flux est ouvert en écriture. Les trois flux possibles sont "w", "x" et "a". À l'instar de read() et readlines(), on utilisera write(chaine) pour écrire une chaîne de caractères et writelines(lignes) avec lignes une liste ou un tuple dont chaque élément est une ligne à écrire. N'oubliez pas le caractère \n en fin de ligne pour revenir à la ligne.
>>> fichier = open("texte.txt",'wt')
>>> fichier.write("Lorem ipsum dolor sit amet, consectetur adipiscing elit. \nPellentesque gravida erat ut lectus convallis auctor. \nFusce mollis sem id tellus auctor hendrerit.")
>>> fichier.close()
>>> fichier = open("texte.txt",'wt')
>>> fichier.writelines(["Lorem ipsum dolor sit amet, consectetur adipiscing elit. \n", "Pellentesque gravida erat ut lectus convallis auctor. \n", "Fusce mollis sem id tellus auctor hendrerit."])
>>> fichier.close()

Formats de fichiers

Il existe différents formats standards de stockage de données. Il est recommandé de favoriser ces formats car il existe déjà des modules Python permettant de simplifier leur utilisation. De plus, ces formats sont adoptés par d'autres programmes avec lesquels vous serez peut-être amené à travailler.

Le format CSV

Le fichier Comma-separated values (CSV) est un format permettant de stocker des tableaux dans un fichier texte. Chaque ligne est représentée par une ligne de texte et chaque colonne est séparée par un séparateur (virgule, point-virgule …).

Les champs texte peuvent également être délimités par des guillemets. Lorsqu'un champ contient lui-même des guillemets, ils sont doublés afin de ne pas être considérés comme début ou fin du champ. Si un champ contient un signe pouvant être utilisé comme séparateur de colonne (virgule, point-virgule …) ou comme séparateur de ligne, les guillemets sont donc obligatoires afin que ce signe ne soit pas confondu avec un séparateur.

Voici des données présentées sous la forme d'un tableau et d'un fichier CSV :

Nom;Prénom;Age
"Dubois";"Marie";29
"Duval";"Julien ""Paul""";47
Jacquet;Bernard;51
Martin;"Lucie;Clara";14

Données sous la forme d'un fichier CSV

NomPrénomAge
DuboisMarie29
DuvalJulien "Paul"47
JacquetBernard51
MartinLucie;Clara14

Données sous la forme d'un tableau

Le module csv de Python permet de simplifier l'utilisation des fichiers CSV.

Lire un fichier CSV

Pour lire un fichier CSV, vous devez ouvrir un flux de lecture de fichier et ouvrir à partir de ce flux un lecteur CSV. Pour ignorer la ligne d'en-tête, utilisez next(lecteurCSV) :

import csv
fichier = open("noms.csv", "rt")
lecteurCSV = csv.reader(fichier,delimiter=";")	# Ouverture du lecteur CSV en lui fournissant le caractère séparateur (ici ";")
for ligne in lecteurCSV:
	print(ligne)	# Exemple avec la 1e ligne du fichier d'exemple : ['Nom', 'Prénom', 'Age']
fichier.close()

Vous obtiendrez en résultat une liste contenant chaque colonne de la ligne en cours. Vous pouvez modifier le délimiteur de champs texte en définissant la variable quotechar :

import csv
fichier = open("noms.csv", "rt")
lecteurCSV = csv.reader(fichier,delimiter=";",quotechar="'")
# Définit l'apostrophe comme délimiteur de champs texte
for ligne in lecteurCSV:
	print(ligne)
fichier.close()

Vous pouvez également lire les données et obtenir un dictionnaire par ligne contenant les données en utilisant DictReader au lieu de reader :

import csv
fichier = open("noms.csv", "rt")
lecteurCSV = csv.DictReader(fichier,delimiter=";")
for ligne in lecteurCSV:
	print(ligne)	# Résultat obtenu : {'Age': '29', 'Nom': 'Dubois', 'Prénom': 'Marie'}
fichier.close()

Écrire un fichier CSV

À l'instar de la lecture, on ouvre un flux d'écriture et on ouvre un écrivain CSV à partir de ce flux :
import csv
fichier = open("annuaire.csv", "wt")
ecrivainCSV = csv.writer(fichier,delimiter=";")
ecrivainCSV.writerow(["Nom","Prénom","Téléphone"])	# On écrit la ligne d'en-tête avec le titre des colonnes
ecrivainCSV.writerow(["Dubois","Marie","0198546372"])
ecrivainCSV.writerow(["Duval","Julien \"Paul\"","0399741052"])
ecrivainCSV.writerow(["Jacquet","Bernard","0200749685"])
ecrivainCSV.writerow(["Martin","Julie;Clara","0399731590"])
fichier.close()

Nous obtenons le fichier suivant :

Nom;Prénom;Téléphone
Dubois;Marie;0198546372
Duval;"Julien ""Paul""";0399741052
Jacquet;Bernard;0200749685
Martin;"Julie;Clara";0399731590

Il est également possible d'écrire le fichier en fournissant un dictionnaire par ligne à condition que chaque dictionnaire possède les mêmes clés. On doit également fournir la liste des clés des dictionnaires avec l'argument fieldnames :

import csv
bonCommande = [
	{"produit":"cahier", "reference":"F452CP", "quantite":41, "prixUnitaire":1.6},
	{"produit":"stylo bleu", "reference":"D857BL", "quantite":18, "prixUnitaire":0.95},
	{"produit":"stylo noir", "reference":"D857NO", "quantite":18, "prixUnitaire":0.95},
	{"produit":"équerre", "reference":"GF955K", "quantite":4, "prixUnitaire":5.10},
	{"produit":"compas", "reference":"RT42AX", "quantite":13, "prixUnitaire":5.25}
]
fichier = open("bon-commande.csv", "wt")
ecrivainCSV = csv.DictWriter(fichier,delimiter=";",fieldnames=bonCommande[0].keys())
ecrivainCSV.writeheader()	# On écrit la ligne d'en-tête avec le titre des colonnes
for ligne in bonCommande:
	ecrivainCSV.writerow(ligne)
fichier.close()

Nous obtenons le fichier suivant :

reference;quantite;produit;prixUnitaire
F452CP;41;cahier;1.6
D857BL;18;stylo bleu;0.95
D857NO;18;stylo noir;0.95
GF955K;4;équerre;5.1
RT42AX;13;compas;5.25

Par défaut, Python placera les guillemets autour des chaînes contenant des guillemets, une virgule ou un point virgule afin que ceux-ci ne soient pas confondus avec un délimiteur de champs ou le séparateur. Afin que tous les champs soient encadrés par les guillemets, nous allons modifier l'argument quoting pour writer ou DictWriter :

import csv
fichier = open("annuaire.csv", "wt")
ecrivainCSV = csv.writer(fichier,delimiter=";",quotechar="'", quoting=csv.QUOTE_ALL)	# quotechar modifie le caractère délimitant un champ (par défaut : ")
ecrivainCSV.writerow(["Nom","Prénom","Téléphone"])	# On écrit la ligne d'en-tête avec le titre des colonnes
ecrivainCSV.writerow(["Dubois","Marie","0198546372"])
ecrivainCSV.writerow(["Duval","Julien \"Paul\"","0399741052"])
ecrivainCSV.writerow(["Jacquet","Bernard","0200749685"])
ecrivainCSV.writerow(["Martin","Julie;Clara","0399731590"])
fichier.close()

Nous obtenons le fichier suivant :

'Nom';'Prénom';'Téléphone'
'Dubois';'Marie';'0198546372'
'Duval';'Julien "Paul"';'0399741052'
'Jacquet';'Bernard';'0200749685'
'Martin';'Julie;Clara';'0399731590'

Le paramètre quoting peut prendre les valeurs suivantes.

ValeurAction
csv.QUOTE_ALLMet tous les champs entre guillemets.
csv.QUOTE_MINIMALMet les guillemets autour des chaînes contenant des guillemets et le séparateur de champs (par défaut).
csv.QUOTE_NONNUMERICMet les guillemets autour des valeurs non-numériques et indique au lecteur de convertir les valeurs non contenues entre les guillemets en nombres réels.
csv.QUOTE_NONENe met aucun guillemet.

Le format JSON

Le format JavaScript Object Notation (JSON) est issu de la notation des objets dans le langage JavaScript. Il s'agit aujourd'hui d'un format de données très répandu permettant de stocker des données sous une forme structurée.

Il ne comporte que des associations clés → valeurs (à l'instar des dictionnaires), ainsi que des listes ordonnées de valeurs (comme les listes en Python). Une valeur peut être une autre association clés → valeurs, une liste de valeurs, un entier, un nombre réel, une chaîne de caractères, un booléen ou une valeur nulle. Sa syntaxe est similaire à celle des dictionnaires Python.

Voici un exemple de fichier JSON :

{
    "Dijon":{
    	"nomDepartement": "Côte d'Or",
    	"codePostal": 21000,
    	"population": {
    		"2006": 151504,
    		"2011": 151672,
    		"2014": 153668
    	}
    },
    "Troyes":{
    	"nomDepartement": "Aube",
    	"codePostal": 10000,
    	"population": {
    		"2006": 61344,
    		"2011": 60013,
    		"2014": 60750
    	}
    }
}

Il est également possible de compacter un fichier JSON en supprimant les tabulations et les retours à la ligne. On obtient ainsi :

{"Dijon":{"nomDepartement":"Côte d'Or","codePostal":21000,"population":{"2006":151504,"2011":151672,"2014":153668}},"Troyes":{"nomDepartement":"Aube","codePostal":10000,"population":{"2006":61344,"2011":60013,"2014":60750}}}

Pour lire et écrire des fichiers JSON, nous utiliserons le module json fourni nativement avec Python.

Lire un fichier JSON

La fonction loads(texteJSON) permet de décoder le texte JSON passé en argument et de le transformer en dictionnaire ou une liste.

>>> import json
>>> fichier = open("villes.json","rt")
>>> villes = json.loads(fichier.read())
>>> print(villes)
{'Troyes': {'population': {'2006': 61344, '2011': 60013, '2014': 60750}, 'codePostal': 10000, 'nomDepartement': 'Aube'}, 'Dijon': {'population': {'2006': 151504, '2011': 151672, '2014': 153668}, 'codePostal': 21000, 'nomDepartement': "Côte d'Or"}}
>>> fichier.close()

Écrire un fichier JSON

On utilise la fonction dumps(variable, sort_keys=False) pour transformer un dictionnaire ou une liste en texte JSON en fournissant en argument la variable à transformer. La variable sort_keys permet de trier les clés dans l'ordre alphabétique.

import json
quantiteFournitures = {"cahiers":134, "stylos":{"rouge":41,"bleu":74}, "gommes": 85}
fichier = open("quantiteFournitures.json","wt")
fichier.write(json.dumps(quantiteFournitures))
fichier.close()

Gestion des erreurs

Lors de l'écriture de vos premiers scripts, vous avez peut-être rencontré ce type de message d'erreurs :

>>> resultat = 42/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Ce message signale l'erreur ainsi que la ligne à laquelle a été faite cette erreur. Or il est courant que les erreurs ne soient pas des erreurs lors de la programmation mais une mauvaise manipulation de la part de l'utilisateur. Par exemple, vous demandez à l'utilisateur de fournir un nombre et celui-ci vous fournit un texte ou que le fichier, que vous cherchez à lire, n'existe pas.

Il faut alors gérer ce type d'erreurs afin d'éviter une interruption involontaire de notre application. Pour cela, nous utiliserons les structures try.

Voici un exemple sur lequel nous allons ajouter un mécanisme de gestion d'erreurs :

age = input("Quel est votre age : ")
age = int(age)

Voici l'erreur obtenue si la chaîne de caractères n'est pas un nombre :

>>> age = input("Quel est votre age : ")
Quel est votre age : Alain
>>> age = int(age)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'Alain'

La structure try se présente ainsi :

try:
	# La portion de code à tester
except:
	# Que faire en cas d'erreur

Nous obtenons donc pour notre exemple :

>>> age = input("Quel est votre age : ")
Quel est votre age : Alain
>>> try:
...     age = int(age)
... except:
...     print("Erreur lors de la saisie. ")
...
Erreur lors de la saisie.

Il est possible d'identifier l'erreur et d'effectuer une action en conséquence. Nous pouvons donc chaîner les instructions except en fournissant le type d'erreur. Le bloc else (optionnel) est exécuté s'il n'y a eu aucune erreur.

try:
	quantiteBoites = quantitePieces / nombresPiecesParBoites
except TypeError:				# Type incompatible avec l'opération
	print("Au moins une des variables n'est pas un nombre. ")
except NameError:				# Variable non définie
	print("Au moins une des variables n'est pas définie. ")
except ZeroDivisionError:	# Division par 0
	print("Le nombres de pièces par boites est égal à 0. ")
else:
	print("Il faut commander " + str(quantiteBoites) + " boîtes. ")

Le bloc finally (optionnel) est exécuté dans tous les cas (s'il y a eu des erreurs ou non).

Enfin, l'instruction pass ne fait rien : elle permet de laisser un bloc vide ce qui est utile pour les exceptions.

Gérer les fichiers

Python fournit deux modules permettant de gérer les fichiers et les répertoires. Le module os.path permet de lister des fichiers et des répertoires, d'effectuer des opérations sur les URI. Le module shutil permet de copier, déplacer, supprimer des éléments sur les systèmes de fichiers.

Dans ce chapitre, nous travaillerons sur des systèmes de fichiers Unix (GNU/Linux, MacOS …).

Les chemins de fichiers

Nous allons étudier les fonctions permettant de manipuler les chemins de fichiers ou de répertoires du module os.path. La fonction basename(URI) renvoie le nom du fichier de l'adresse fourni en argument. À l'inverse, la fonction dirname(URI) renvoie le chemin jusqu'au fichier, sans le nom du fichier. Ces fonctions fonctionnent même si le fichier n'existe pas.

>>> import os.path
>>> chemin = "/tmp/dir/dir2/monFichier.txt"
>>> print(os.path.basename(chemin))
monFichier.txt
>>> print(os.path.dirname(chemin))
/tmp/dir/dir2

Différentier les fichiers et les répertoires

La fonction exists(URI) renvoie True si le fichier ou le répertoire fournis en argument existent. Les fonctions isfile(URI) et isdir(URI) permettent respectivement de vérifier si le chemin mène à un fichier et un répertoire. Ces fonctions renvoient True si c'est le cas.

>>> import os.path
>>> chemin = "/tmp/dir/dir2/monFichier.txt"
>>> print(os.path.exists(chemin))
True
>>> print(os.path.isfile(chemin))
True
>>> print(os.path.isdir(chemin))
False
>>> print(os.path.isdir(os.path.dirname(chemin)))
True

Lister le contenu d'un répertoire

La fonction listdir(repertoire) du module os.path retourne le contenu du répertoire passé en argument sans distinction entre les fichiers et les répertoires.

>>> import os.path
>>> print(os.listdir("/tmp/dir"))
['villes.json', 'quantiteFournitures.json', 'dir2']

Copier un fichier ou un répertoire

Il existe deux méthodes dans le module shutil permettant d'effectuer une copie. La fonction copy(source, destination) permet de copier un fichier, alors que copytree en fait de même avec les répertoires.

import shutil
shutil.copytree("/tmp/dir/dir2","/tmp/dir/dir3")
shutil.copy("/tmp/dir/dir2/monFichier.txt","/tmp/dir/exemple.txt")

Déplacer un fichier ou un répertoire

La fonction move(source, destination) du module shutil permet de déplacer un fichier ou un répertoire. Cela peut également servir à renommer le fichier ou le répertoire.

import shutil
shutil.move("/tmp/dir/dir3","/tmp/dir/perso")

Supprimer un fichier ou un répertoire

La méthode remove(fichier) du module os et la fonction rmtree(dossier) du module shutil permettent respectivement de supprimer un fichier et un répertoire.

import os,shutil
os.remove("/tmp/dir/exemple.txt")
shutil.rmtree("/tmp/dir/perso")

Sauvegarder des variables

Le module pickle permet de sérialiser des variables quelles qu'elles soient vers un fichier ouvert en flux binaire et de les restaurer. Cela équivaut à sauvegarder et restaurer l'état des variables.

La fonction dump(variable, fichier) permet d'exporter une variable vers un fichier et la fonction load(fichier) retourne la variable lue depuis le fichier.

L'ordre de sauvegarde et de restauration des variables doit être identique.

>>> import pickle
>>> texte = "Écrit par Antoine de Saint-Exupéry"
>>> quantiteFournitures = {"cahiers":134, "stylos":{"rouge":41,"bleu":74}, "gommes": 85}
>>> fournitures = ["cahier", "crayon", "stylo", "trousse", "gomme"]
>>> fichierSauvegarde = open("donnees","wb")
>>> pickle.dump(texte, fichierSauvegarde)
>>> pickle.dump(quantiteFournitures, fichierSauvegarde)
>>> pickle.dump(fournitures, fichierSauvegarde)
>>> fichierSauvegarde.close()
>>> import pickle
>>> fichierSauvegarde = open("donnees","rb")
>>> texte = pickle.load(fichierSauvegarde)
>>> quantiteFournitures = pickle.load(fichierSauvegarde)
>>> fournitures = pickle.load(fichierSauvegarde)
>>> fichierSauvegarde.close()
>>> print(texte)
Écrit par Antoine de Saint-Exupéry
>>> print(quantiteFournitures)
{'stylos': {'bleu': 74, 'rouge': 41}, 'gommes': 85, 'cahiers': 134}
>>> print(fournitures)
['cahier', 'crayon', 'stylo', 'trousse', 'gomme']

Exercices

  1. Écrivez un programme permettant à un utilisateur de deviner un mot choisi au hasard dans un fichier nommé mots.txt dans lequel chaque ligne comporte un mot en capitale. L'utilisateur a 7 chances pour découvrir le mot en proposant une lettre à chaque fois. Si la lettre proposée n'est pas dans le mot, une chance lui est retirée.

    Exemple :

    - - - - - - - - (7 chances)
    Entrez une lettre : S
    - - - - - - - - (6 chances)
    Entrez une lettre : O
    - O - - - - - - (6 chances)
    …
    Bravo ! Le mot était JOURNAUX. 
    ou
    Perdu ! Le mot était JOURNAUX. 
    

    Vous pouvez télécharger un fichier de mots ici : Télécharger le fichier

    Démarrer l'activité avec Python Studio

  2. Écrivez un programme permettant de chiffrer un fichier texte nommé texte.txt à l'aide du chiffrement par décalage dans lequel chaque lettre est remplacée par une autre à distance fixe choisie par l'utilisateur. Par exemple, si la distance choisie est de 4, un A est remplacé par un D, un B par un E, un C par un F … Le résultat sera écrit dans un fichier texte nommé chiffre.txt.

    Texte en clairESOPE RESTE ICI ET SE REPOSE
    Texte chiffré (distance 7)LZVWL YLZAL PJP LA ZL YLWVZL

    Démarrer l'activité avec Python Studio

  3. Écrivez un programme permettant à partir d'un fichier texte en français d'en déduire la fréquence d'apparition des lettres qu'il contient. Le résultat de cette analyse sera consigné dans un fichier JSON. Pour des statistiques fiables, prenez un texte assez long. Vous pouvez utiliser une copie de Zadig, écrit par Voltaire, disponible ici : Télécharger le fichier

    Démarrer l'activité avec Python Studio

  4. Le message suivant a été chiffré à l'aide de la technique de chiffrement par décalage et stocké dans le fichier chiffre.txt :

    HFCMSG GS GWHIS ROBG ZS UFOBR SGH RS ZO TFOBQS OI 
    QSBHFS RI RSDOFHSASBH RS Z OIPS RCBH SZZS SGH ZS 
    QVST ZWSI SH O Z CISGH RS ZO FSUWCB UFOBR SGH ZO 
    QCAAIBS G SHSBR ROBG ZO DZOWBS RS QVOADOUBS 
    QFOMSIGS O DFCLWAWHS RI DOMG R CHVS SH RI DOMG R 
    OFAOBQS QSHHS JWZZS RS DZOWBS OZZIJWOZS G SHOPZWH 
    ROBG ZO JOZZSS RS ZO GSWBS
    

    À l'aide des statistiques d'apparition des lettres issues de l'exercice précédent stockées dans le fichier statistiques.json, déduisez le message en clair du texte ci-dessus et stockez le dans le fichier clair.txt.

    Démarrer l'activité avec Python Studio

  5. Écrivez un programme permettant de calculer la moyenne d'un étudiant dont les notes sont consignées dans un fichier CSV nommé notes.csv. Chaque colonne correspond à une matière. Vous devrez écrire un fichier JSON nommé moyennes.json consignant ces moyennes ainsi que la moyenne générale. Toutes les notes et les matières sont au cœfficient 1. Un exemple de fichier CSV est disponible ici : Télécharger le fichier

    Exemple :

    {
    	"Mathématiques": 8.411111111111111, 
    	"Sciences naturelles": 17.412, 
    	…
    	"Moyenne générale": 14.965248762650994
    }
    

    Démarrer l'activité avec Python Studio