|
Cours Python avec exercices écrit par Christophe Darmangeat dans le cadre du M2 PiSE |
|
Les fichiersNB : on se limitera dans ce chapitre au traitement des fichiers texte. Ouverture et fermetureL'ouverture s'effectue via la fonction open(), qui retourne le fichier sous forme d'objet. On reconnapit les trois modes d'ouverture :
Jamais à court de ressources, Python en propose trois autres :
Pour utiliser un fichier en mode binaire, on rajoute 'b' aux codes précédents (par exemple, 'rb' pour lire un fichier binaire). La fermeture s'effectue via la méthode close(). L'ensemble donne donc une syntaxe standard :
monFichier = open('chemin/fichier.txt', mode, encoding='utf-8') Lecture(s) d'un fichierLa lecture d'un fichier peut se faire ligne par ligne ou d'un seul coup, au choix du programmeur. Pour une lecture ligne à ligne, la solution la plus proche de l'algorithmique consiste à employer la méthode readline(). Lorsqu'on atteint la fin du fichier, celle-ci renvoie une chaîne vide. Toutefois, un premier problème se pose : si python découpe bien le fichier en fonction du caractère de fin de ligne, à savoir \n, en revanche il inclut systématiquement ce caractère dans la séquence récupérée. Il faudra donc systématiquement le purger, le plus simple étant d'utiliser la méthode rsplit. On pourra donc écrire quelque chose du genre :
fichier = open('donnees.txt', 'r', encoding='utf-8') Python propose toutefois une autre manière de procéder, liée au fait qu'un objet fichier est par nature dit « itérable » (comme une liste, ou une chaîne). On peut donc lui appliquer l'instruction de boucle déjà rencontrée plusieurs fois :
fichier = open('donnees.txt', 'r', encoding='utf-8') Dans ces exemples, on s'est contenté d'afficher le contenu du fichier à l'écran. Mais pour traiter les informations qu'il contient, on peut souhaiter ranger les lignes dans une liste. Ceci peut se faire via une instruction append à l'intérieur de la boucle :
fichier = open('donnees.txt', 'r', encoding='utf-8') Python propose également une instruction qui économise toute boucle, en la gérant automatiquement : il s'agit de la méthode readlines – au pluriel, donc. Démonstration :
fichier = open('donnees.txt', 'r', encoding='utf-8') Faut-il préférer l'approche ligne à ligne, via une boucle, ou l'approche globale, via readlines ? À titre personnel, et même lorsque la méthode rapide ne semble poser aucun problème, j'ai une préférence marquée pour l'approche itérative, consistant à lire les lignes une par une via une boucle. D'une part, celle-ci permet de traiter d'emblée le problème du \n final de chaque ligne ; d'autre part, cette méthode permet un contrôle plus fin sur le processus, bien utile lorsque le fichier est volumineux ou que sa lecture est susceptible de générer des erreurs. Écriture(s) d'un fichierFaut-il le rappeler, pour écrire dans un fichier, il faut avoir pris soin de l'ouvrir dans un des modes permettant cette opération (w, a ou r+). Pour un fichier texte, l'écriture d'une ligne constituée d'une chaîne de caractères se fait par la méthode write. En toute logique étant donné ce qui précède, le code devra procéder à l'ajout du saut de ligne final \n, qui n'est pas géré automatiquement.
# on suppose qu'on a une liste de lignes appelée listeLignes Tout comme on peut lire d'un seul coup un fichier texte en récupérant les lignes dans une liste avec Readlines(), on peut en sens inverse écrire d'un seul coup une liste dans un fichier sous forme de lignes, avec la méthode Writelines() – là encore, le saut de ligne final doit impérativement figurer dans les chaînes de caractères envoyées à l'écriture :
listeLignes = ['Ligne 1\n', 'Ligne 2\n', 'Ligne 3\n'] Structuration en champsDans un fichier texte, on cherche très souvent à mémoriser l'équivalent d'une table en base de données, c'est-à-dire qu'on ne se contente pas de différents individus ou objets (les enregistrements) mais, pour chacun d'eux, on renseigne différentes informations (les champs). La structuration en ligne correspond aux enregistrements ; reste à trouver le moyen de stocker et restituer les différents champs. Pour ce faire, il existe deux grandes stratégies : oprére soit avec des champs de largeur fixe, soit avec des champs délimités (par un caractère choisi comme séparateur). En python, les deux méthodes sont possibles, mais la plus facile à programmer est clairement celle des champs délimités. Dans le cas le plus fréquent, on aura besoin de stocker l'intégralité du fichier en mémoire afin de travailler sur les informations, avant de les enregistrer à nouveau. Il faut donc se poser la question du type de données à utiliser à cette fin. Prenons l'exemple classique d'un petit annuaire personnel, où pour chaque individu nous voulons gérer le nom, le prénom, le mail et le numéro de téléphone. Solution 1 : autant de listes que de champsUne première solution (qui n'est mentionnée ici que pour être écartée aussitôt) serait de construire quatre listes nom[ ], prenom[ ], email[ ] et tel[ ], puis de veiller ensuite à les gérer de manière coordonnée, que ce soit pour l'ajout ou pour la suppression : le 27e élément de l'une doit toujours bien correspondre au 27e élément des trois autres ! On voit tout de suite qu'un tel choix n'est guère judicieux. En plus de sa lourdeur, il est extrêmement propice aux erreurs de programmation. Solution 2 : une liste de listesUne option nettement meilleure sera d'utiliser une liste de listes – l'équivalent en python d'un tableau en deux dimensions. Une fois chargé en mémoire, notre carnet d'adresse ressemblera donc à ceci :
[ Cette dernière solution est assez performante. Son principal inconvénient est que pour la suite, on ne pourra pas désigner chaque information par un intitulé (nom, prénom, etc.) : on sera obligé de passer par des indices (0 pour le nom, 1 pour le prénom, etc.) ce qui handicape la lisibilité du code. Le nom du troisième individu s'écrit par exemple : listeFichier[2][0] Solution 3 : une liste de dictionnairesOn peut donc également envisager deux solutions apparemment un peu plus lourdes, mais qui ont l'avantage de pallier cet inconvénient. La première consiste à utiliser, au lieu d'une liste de listes, une liste de dictionnaires :
[ Avec cette architecture, on pourra faire apparaître en clair les champs en tant que clés, en demandant par exemple le mail du deuxième individu : listeFichier[1]['email'] Solution 4 : une liste de structuresUne autre possibilité, qui n'a été implémentée que dans les versions relativement récentes de python, consiste, sur le modèle de ce que nous avons rencontré en algorithmique, à constituer à partir du fichier une liste de structures – et comme on est en programmation objet, une structure est une classe (sans méthodes) :
from dataclasses import dataclass Ainsi, on pourra ensuite obtenir le numéro de téléphone du huitième individu en demandant mesAdresses[7].tel Recopier les données du fichier en mémoire viveBien évidemment, les instructions qui vont permettre de recopier les informations depuis le fichier vers la mémoire vive vont varier selon le type de données choisies pour les accueillir. En supposant qu'à chaque fois, on ait opté pour une itération ligne à ligne, voici les différentes déclinaisons de notre exemple. Vers une liste de listesOn va pouvoir utiliser un outil déjà signalé dans un chapitre précédent : la méthode split(), qui permet de découper automatiquement une chaîne selon un caractère de délimitation pour en faire une liste. En admettant que dans notre fichier, nous ayons opté pour une délimitation par des points-virgules, nous aurons :
fichier = open('donnees.txt', 'r', encoding='utf-8') Vers une liste de dictionnairesLà, il s'agit d'écrire à chaque fois les bonnes valeurs des clés et des valeurs. On crée donc la liste des clés (qui pourrait exister comme en-tête de colonnes, dans la première ligne du fichier : mais ce n'est pas cette option que l'on a choisi dans cet exemple) :
fichier = open('donnees.txt', 'r', encoding='utf-8') Vers une liste de structuresPour chaque ligne du fichier, il s'agit ici d'envoyer les informations vers les quatre champs qui constituent la structure, puis d'ajouter celle-ci à la liste :
from dataclasses import dataclass Ex 1 : Notes On part d'un fichier notes.txt constitué comme suit :Alice, 14 Marc, 9 Julie, 16 Marc, 12 Alice, 18 Créer un programme qui lit le fichier, puis construit et affiche un dictionnaire associant à chaque élève la liste de ses notes : { 'Alice': [14,18], 'Marc': [9,12], 'Julie': [16] } Le programme constituera ensuite un nouveau fichier associant à chaque étudiant sa moyenne : Alice, 16 Marc, 10.5 Julie, 16 Ex 2 : Meilleurs scores On joue au jeu le plus idiot de la terre : il consiste à lancer 10 dés et à additionner les points obtenus. Toutefois, on tient le compte des dix meilleurs scores jamais réalisés. À chaque partie, la machine devra donc vérifier si le score réalisé par le joueur entre dans le Top 10. Dans ce cas, elle lui demandera son nom, puis affichera un message tel que : « Félicitations ! Vous intégrez le Hall of Fame. » La machine affiche ensuite les dix meilleurs scores (par ordre décroissant), avec pour chaque d'eux le nom et le score. Le gestionnaire de fichiers .csvLes techniques ci-dessus, qui passent par le split possèdent l'avantage d'une certaine légèreté. Elles ont néanmoins un inconvénient majeur : celui de ne fonctionner qu'à condition que la structure du fichier soit toujours parfaitement régulière. Il suffira par exemple qu'un des champs du fichier contienne des guillemets, un saut de ligne ou le caractère séparateur pour qu'un erreur se produise. Le module csv, lui, gère beaucoup de cas problématiques qui apparaissent très vite dans des données sur lequelles on n'a pas un contrôle parfait, ce qui est souvent le cas. Avec une liste de listesOn ne reprendra pas ici l'intégralité des solutions proposées précédemment. Regardons simplement ce qui se passe lorsqu'on décide de gérer le fichier avec une liste de liste - et dorénavant, on décide que la première ligne du fichier contiendra les noms de champs. Les deux instructions utiles sont writerow (pour écrire une seule ligne) et writerows (pour écrire une série de lignes à partir d'une liste). Ainsi, si notre fichier contient une petite base de données sur des films, du genre :
titre,annee,realisation,note La lecture s'effectuera ainsi :
import csv On notera que dans ce cas, en mémoire vive, on a transformé deux des champs texte en numériques (entiers). On reprend donc cette convention pour l'écriture, à partir d'une liste mélangeant les deux types de données :
import csv Avec une liste de dictionnairesDans le cas où on a choisi d'utiliser en mémoire vive une liste de dictionnaires, il existe alors deux armes fatales. Pour la lecture, DictReader :
import csv NB : si le délimiteur est autre que la virgule, on peut le préciser sous forme d'argument supplémentaire dans DictReader : lecteur = csv.DictReader(fichier, delimiter=';') Quant à l'écriture, elle s'effectue grâce à la méthode inverse, DictWriter :
import csv Techniques supplémentairesPrévenir les erreursUn des problèmes qui se posent lorsqu'on veut utiliser un fichier en lecture dans un programme est sa possible absence de l'emplacement programmé. Aussi est-il est sage d'ouvrir le parapluie et de prévenir qu'une telle erreur interrompe l'exécution du programme. Python propose pour cela un bloc try, qui permet en quelque sorte d'attraper une erreur au vol avant qu'elle n'interrompe l'exécution, et de la gérer via l'instruction except. On pourra ainsi écrire quelque chose du genre :
try: Économiser l'instruction closeBien des programmeurs aiment économiser l'instruction Close() en ouvrant le fichier via l'instruction With. Ainsi, au lieu d'écrire comme précédemment :
monFichier = open('chemin/fichier.txt', mode) On aura à présent :
with open('chemin/fichier.txt', mode) as monFichier: Ex 3 : Notes étudiantes On travaille à partir d'un fichier où chaque ligne correspond à une inscription à un cours :Etudiant;Cours;Note Alice;Python;15 Marc;Python;12 Alice;Statistiques;14 Julie;Python;16 Marc;Bases_de_donnees;13 Alice;Bases_de_donnees;15 Julie;Statistiques;17 Le programme doit créer un nouveau fichier structuré où chaque étudiant n’apparaît qu’une seule fois, et où les notes sont regroupées par cours (si l'étudiant n'a pas suivi un cours, le champ correspondant reste vide) : Etudiant;Python;Statistiques;Bases_de_donnees Alice;15;14;15 Marc;12;;13 Julie;16;17 |
|