Programmation Python/Fichiers
Jusqu'à présent, les programmes que nous avons réalisés ne traitaient qu'un très petit nombre de données. Nous pouvions donc à chaque fois inclure ces données dans le corps du programme lui-même (par exemple dans une liste). Cette façon de procéder devient cependant tout à fait inadéquate lorsque l'on souhaite traiter une quantité d'information plus importante.
Utilité des fichiers.
Imaginons par exemple que nous voulons écrire un petit programme exerciseur qui fasse apparaître à l'écran des questions à choix multiple, avec traitement automatique des réponses de l'utilisateur. Comment allons-nous mémoriser le texte des questions elles-mêmes ?
L'idée la plus simple consiste à placer chacun de ces textes dans une variable, en début de programme, avec des instructions d'affectation du genre :
a = "Quelle est la capitale du Guatémala ?"
b = "Qui a succédé à Henri IV ?"
c = "Combien font 26 × 43 ?"
 etc.
Cette idée est malheureusement beaucoup trop simpliste. Tout va se compliquer en effet lorsque nous essayerons d'élaborer la suite du programme, c'est-à-dire les instructions qui devront servir à sélectionner au hasard l'une ou l'autre de ces questions pour les présenter à l'utilisateur. Employer par exemple une longue suite d'instructions codice_1 comme dans l'exemple ci-dessous n'est certainement pas la bonne solution (ce serait d'ailleurs bien pénible à écrire : n'oubliez pas que nous souhaitons traiter un grand nombre de questions !) :
if choix == 1:
 selection = a
elif choix == 2:
 selection = b
elif choix == 3:
 selection = c
 ... etc.
La situation se présente déjà beaucoup mieux si nous faisons appel à une liste :
liste = ["Qui a vaincu Napoléon à Waterloo ?",
 "Comment traduit-on 'informatique' en anglais ?",
 "Quelle est la formule chimique du méthane ?", ... etc ...]
On peut en effet extraire n'importe quel élément de cette liste à l'aide de son indice. Exemple :
print liste[2] ===> "Quelle est la formule chimique du méthane ?"
Même si cette façon de procéder est déjà nettement meilleure que la précédente, nous sommes toujours confrontés à plusieurs problèmes gênants :
Cette dernière remarque nous suggère la direction à prendre : il est temps que nous apprenions à "séparer les données, et les programmes qui les traitent, dans des fichiers différents".
Pour que cela devienne possible, nous devrons doter nos programmes de divers mécanismes permettant de créer des fichiers, d'y envoyer des données et de les récupérer par après.
Les langages de programmation proposent des jeux d'instructions plus ou moins sophistiqués pour effectuer ces tâches. Lorsque les quantités de données deviennent très importantes, il devient d'ailleurs rapidement nécessaire de structurer les relations entre ces données, et l'on doit alors élaborer des systèmes appelés "bases de données relationnelles", dont la gestion peut s'avérer très complexe. Ce sera là l'affaire de logiciels très spécialisés tels que "Oracle", "IBM DB", "Adabas", "PostgreSQL", "MySQL", etc. Python est parfaitement capable de dialoguer avec ces systèmes, mais nous laisserons cela pour un peu plus tard (voir chapitre Gestion d'une base de données).
Nos ambitions présentes sont plus modestes. Nos données ne se comptent pas encore par centaines de milliers, aussi nous pouvons nous contenter de mécanismes simples pour les enregistrer dans un fichier de taille moyenne, et les en extraire ensuite.
Travailler avec des fichiers.
L'utilisation d'un fichier ressemble beaucoup à l'utilisation d'un livre. Pour utiliser un livre, vous devez d'abord le trouver (à l'aide de son titre), puis l'ouvrir. Lorsque vous avez fini de l'utiliser, vous le refermez. Tant qu'il est ouvert, vous pouvez y lire des informations diverses, et vous pouvez aussi y écrire des annotations, mais généralement vous ne faites pas les deux à la fois. Dans tous les cas, vous pouvez vous situer à l'intérieur du livre, notamment en vous aidant des numéros de pages. Vous lisez la plupart des livres en suivant l'ordre normal des pages, mais vous pouvez aussi décider de consulter n'importe quel paragraphe dans le désordre.
Tout ce que nous venons de dire des livres s'applique aussi aux fichiers informatiques.
Un fichier se compose de données enregistrées sur votre disque dur, sur une disquette ou sur un CD-ROM. Vous y accédez grâce à son nom (lequel peut inclure aussi un nom de répertoire). Vous pouvez toujours considérer le contenu d'un fichier comme une suite de caractères, ce qui signifie que vous pouvez traiter ce contenu, ou une partie quelconque de celui-ci, à l'aide des fonctions servant à traiter les chaînes de caractères.
Noms de fichiers - Répertoire courant.
Pour simplifier les explications qui vont suivre, nous indiquerons seulement des noms simples pour les fichiers que nous allons manipuler. Si vous procédez ainsi dans vos exercices, les fichiers en question seront créés et/ou recherchés par Python "dans le répertoire courant". Celui-ci est habituellement le répertoire où se trouve le script lui-même, sauf si vous lancez ce script depuis la fenêtre d'un shell IDLE, auquel cas le répertoire courant est défini au lancement de IDLE lui-même (Sous Windows, la définition de ce répertoire fait partie des propriétés de l'icône de lancement).
Si vous travaillez avec IDLE, vous souhaiterez donc certainement forcer Python à changer son répertoire courant, afin que celui-ci corresponde à vos attentes. Pour ce faire, utilisez les commandes suivantes en début de session. (Nous supposons ici que le répertoire visé est le répertoire codice_2. Vous pouvez franchement utiliser cette syntaxe (c'est-à-dire des caractères / et non \ en guise de séparateurs : c'est la convention en vigueur dans le monde Unix). Python effectuera automatiquement les conversions nécessaires, suivant que vous travaillez sous MacOS, Linux, ou Windows.
»> from os import chdir
»> chdir("/home/jules/exercices")
La première commande importe la fonction codice_3 du module codice_4. Le module codice_4 contient toute une série de fonctions permettant de dialoguer avec le système d'exploitation (os = "operating system"), quel que soit celui-ci.
La seconde commande provoque le changement de répertoire (codice_6 = "change directory")
Les deux formes d'importation.
Les lignes d'instructions que nous venons d'utiliser sont l'occasion d'expliquer un mécanisme intéressant. Vous savez qu'en complément des fonctions intégrées dans le module de base, Python met à votre disposition une très grande quantité de fonctions plus spécialisées, qui sont regroupées dans des "modules". Ainsi vous connaissez déjà fort bien le module codice_7 et le module codice_8.
Pour utiliser les fonctions d'un module, il suffit de les importer. Mais cela peut se faire de deux manières différentes, comme nous allons le voir ci-dessous. Chacune des deux méthodes présente des avantages et des inconvénients.
Voici un exemple de la première méthode :
»»» import os
»> rep_cour = os.getcwd()
»> print rep_cour
C:\Python22\essais
La première ligne de cet exemple importe l'intégralité du module codice_4, lequel contient de nombreuses fonctions intéressantes pour l'accès au système d'exploitation. La seconde ligne utilise la fonction codice_10 du module os. Comme vous pouvez le constater, la fonction codice_10 renvoie le nom du répertoire courant (getcwd = "get current working directory").
Par comparaison, voici un exemple similaire utilisant la seconde méthode d'importation :
»> from os import getcwd
»> rep_cour = getcwd() 
»> print rep_cour
C:\Python22\essais
Dans ce nouvel exemple, nous n'avons importé du module codice_4 que la fonction codice_10 seule. Importée de cette manière, la fonction s'intègre à notre propre code comme si nous l'avions écrite nous-mêmes. Dans les lignes où nous l'utilisons, il n'est pas nécessaire de rappeler qu'elle fait partie du module codice_4.
Nous pouvons de la même manière importer plusieurs fonctions du même module :
»> from math import sqrt, pi, sin, cos
»> print pi
3.14159265359
»> print sqrt(5) # racine carrée de 5
2.2360679775
»> print sin(pi/6) # sinus d'un angle de 30°
0.5
Nous pouvons même importer toutes les fonctions d'un module, comme dans :
from Tkinter import *
Cette méthode d'importation présente l'avantage d'alléger l'écriture du code. Elle présente l'inconvénient (surtout dans sa dernière forme, celle qui importe toutes les fonctions d'un module) d'encombrer l'espace de noms courant. Il se pourrait alors que certaines fonctions importées aient le même nom que celui d'une variable définie par vous-même, ou encore le même nom qu'une fonction importée depuis un autre module. (Si cela se produit, l'un des deux noms en conflit n'est évidemment plus accessible).
Dans les programmes d'une certaine importance, qui font appel à un grand nombre de modules d'origines diverses, il sera donc toujours préférable de privilégier plutôt la première méthode, c'est-à-dire celle qui utilise des noms pleinement qualifiés.
On fait généralement exception à cette règle dans le cas particulier du module codice_8, parce que les fonctions qu'il contient sont très sollicitées (dès lors que l'on décide d'utiliser ce module).
Écriture séquentielle dans un fichier.
Sous Python, l'accès aux fichiers est assuré par l'intermédiaire d'un "objet-fichier" que l'on crée à l'aide de la fonction interne codice_16. Après avoir appelé cette fonction, vous pouvez lire et écrire dans le fichier en utilisant les méthodes spécifiques de cet objet-fichier.
L'exemple ci-dessous vous montre comment ouvrir un fichier « en écriture », y enregistrer deux chaînes de caractères, puis le refermer. Notez bien que si le fichier n'existe pas encore, il sera créé automatiquement. Par contre, si le nom utilisé concerne un fichier préexistant qui contient déjà des données, les caractères que vous y enregistrerez viendront s'ajouter à la suite de ceux qui s'y trouvent déjà. Vous pouvez faire tout cet exercice directement à la ligne de commande :
file_path = 'mon_dossier/mon_fichier'
if (os.path.isfile(file_path)):
 obFichier = open(file_path,'a')
 obFichier.write('Bonjour, fichier !')
 obFichier.write("Quel beau temps, aujourd'hui !")
 obFichier.close()
Lecture séquentielle d'un fichier.
Vous allez maintenant rouvrir le fichier, mais cette fois « en lecture », de manière à pouvoir y relire les informations que vous avez enregistrées dans l'étape précédente :
»> ofi = open('Monfichier', 'r')
»> t = ofi.read()
»> print t
Bonjour, fichier ! Quel beau temps, aujourd'hui !
»> ofi.close()
Comme on pouvait s'y attendre, la méthode codice_26 lit les données présentes dans le fichier et les transfère dans une variable de type « chaîne » ("string") . Si on utilise cette méthode sans argument, la totalité du fichier est transférée.
Exemple avec une boucle.
Il va de soi que les boucles de programmation s'imposent lorsque l'on doit traiter un fichier dont on ne connaît pas nécessairement le contenu à l'avance. L'idée de base consistera à lire ce fichier morceau par morceau, jusqu'à ce que l'on ait atteint la fin du fichier.
La fonction ci-dessous illustre cette idée. Elle copie l'intégralité d'un fichier, quelle que soit sa taille, en transférant des portions de 50 caractères à la fois :
def copieFichier(source, destination):
 "copie intégrale d'un fichier" 
 fs = open(source, 'r')
 fd = open(destination, 'w')
 while 1:
 txt = fs.read(50)
 if txt =="":
 break
 fd.write(txt)
 fs.close()
 fd.close()
 return
Si vous voulez tester cette fonction, vous devez lui fournir deux arguments : le premier est le nom du fichier original, le second est le nom à donner au fichier qui accueillera la copie. Exemple :
copieFichier('Monfichier','Tonfichier')
Fichiers texte.
Un fichier texte est un fichier qui contient des caractères imprimables et des espaces organisés en lignes successives, ces lignes étant séparées les unes des autres par un caractère spécial non-imprimable appelé « marqueur de fin de ligne ».
Il est très facile de traiter ce genre de fichiers sous Python. Les instructions suivantes créent un fichier texte de quatre lignes :
»> f = open("Fichiertexte", "w")
»> f.write("Ceci est la ligne un\nVoici la ligne deux\n")
»> f.write("Voici la ligne trois\nVoici la ligne quatre\n")
»> f.close()
Notez bien le marqueur de fin de ligne codice_27 inséré dans les chaînes de caractères, aux endroits où l'on souhaite séparer les lignes de texte dans l'enregistrement. Sans ce marqueur, les caractères seraient enregistrés les uns à la suite des autres, comme dans les exemples précédents.
Lors des opérations de lecture, les lignes d'un fichier texte peuvent être extraites séparément les unes des autres. La méthode codice_28, par exemple, ne lit qu'une seule ligne à la fois (en incluant le caractère de fin de ligne) :
»> f = open('Fichiertexte','r')
»> t = f.readline()
»> print t
Ceci est la ligne un
»> print f.readline()
Voici la ligne deux
La méthode codice_29 transfère toutes les lignes restantes dans une liste de chaînes :
»> t = f.readlines()
»> print t
['Voici la ligne trois\012', 'Voici la ligne quatre\012']
»> f.close()
Le script qui suit vous montre comment créer une fonction destinée à effectuer un certain traitement sur un fichier texte. En l'occurrence, il s'agit ici de recopier un fichier texte en omettant toutes les lignes qui commencent par un caractère codice_37 :
def filtre(source,destination):
 "recopier un fichier en éliminant les lignes de remarques"
 fs = open(source, 'r')
 fd = open(destination, 'w')
 while 1:
 txt = fs.readline()
 if txt ==":
 break
 if txt[0] != '#':
 fd.write(txt)
 fs.close()
 fd.close()
 return
Pour appeler cette fonction, vous devez utiliser deux arguments : le nom du fichier original, et le nom du fichier destiné à recevoir la copie filtrée. Exemple :
filtre('test.txt', 'test_f.txt')
Enregistrement et restitution de variables diverses.
L'argument de la méthode codice_23 doit être une chaîne de caractères. Avec ce que nous avons appris jusqu'à présent, nous ne pouvons donc enregistrer d'autres types de valeurs qu'en les transformant d'abord en chaînes de caractères.
Nous pouvons réaliser cela à l'aide de la fonction intégrée codice_39 :
»> x = 52
»> f.write(str(x))
Si nous enregistrons les valeurs numériques en les transformant d'abord en chaînes de caractères, nous risquons de ne plus pouvoir les re-transformer correctement en valeurs numériques lorsque nous allons relire le fichier. Exemple :
»> a = 5
»> b = 2.83
»> c = 67
»> f = open('Monfichier', 'w')
»> f.write(str(a))
»> f.write(str(b))
»> f.write(str(c))
»> f.close()
»> f = open('Monfichier', 'r')
»> print(f.read())
52.8367
»> f.close()
Nous avons enregistré trois valeurs numériques. Mais comment pouvons-nous les distinguer dans la chaîne de caractères résultante, lorsque nous effectuons la lecture du fichier ? C'est impossible ! Rien ne nous indique d'ailleurs qu'il y a là trois valeurs plutôt qu'une seule, ou 2, ou 4,…
Il existe plusieurs solutions à ce genre de problèmes. L'une des meilleures consiste à importer un module Python spécialisé : le module pickle. Voici comment il s'utilise :
»> import pickle
»> f = open('Monfichier', 'wb')
»> pickle.dump(a, f)
»> pickle.dump(b, f)
»> pickle.dump(c, f)
»> f.close()
»> f = open('Monfichier', 'rb')
»> t = pickle.load(f)
»> print(t, type(t))
5 <type 'int'>
»> t = pickle.load(f)
»> print(t, type(t))
2.83 <type 'float'>
»> t = pickle.load(f)
»> print(t, type(t))
67 <type 'int'>
»> f.close()
Pour cet exemple, on considère que les variables codice_20, codice_41 et codice_42 contiennent les mêmes valeurs que dans l'exemple précédent. La fonction codice_43 du module "pickle" attend deux arguments : le premier est la variable à enregistrer, le second est l'objet fichier dans lequel on travaille. La fonction codice_44 effectue le travail inverse, c'est-à-dire la restitution de chaque variable avec son type.
Vous pouvez aisément comprendre ce que font exactement les fonctions du module "pickle" en effectuant une lecture « classique » du fichier résultant, à l'aide de la méthode codice_26 par exemple.
Lister des fichiers.
La fonction codice_46 liste les fichiers du dossier courant, ou ceux du dossier en paramètre s'il est mentioné.
Lister des fichiers et dossiers.
 items = os.walk(folder)
 for root, directories, files in items:
 for directory in directories:
 print(directory)
 for file in files:
 print(file)
 return
Créer un dossier s'il n'existe pas.
file_path = 'subfolder/test.txt'
parent_folder = os.path.dirname(file_path)
if not os.path.exists(parent_folder):
 os.mkdir(parent_folder)
file_object = codecs.open(fichier, 'a', 'utf-8')
Supprimer des fichiers et dossiers.
Pour un fichier :
Pour un dossier vide :
Pour un dossier non vide :