Appliquer un traitement à tous les fichiers d’un dossier en Python


Opération courante en informatique et on a tous eu besoin de chercher comment faire une fois.

Soit l’arborescence :

test
├── dossier
│   ├── fichier.py
│   ├── fichier.txt
│   └── pas_un_dossier.txt
├── Dossier
│   ├── dOssier
│   │   └── faichier
│   └── fichiiiiiiiiier
├── .fichier
├── fichier
├── fIchier
└── Fichier

Lister le contenu d’un dossier

>>> import os
>>> os.listdir('.')
['dossier', 'Dossier', 'fichier', 'fIchier', '.fichier', 'Fichier']

On récupère les noms des dossiers et les fichiers, y compris cachés, mais pas les dossiers spéciaux types .. et ..

Le type des noms retournés est str que ce soit en Python 2 ou 3. Ça a l’air cool et homogène comme ça, jusqu’à ce qu’on se souvienne que str sont des bits en Python 2 et de l’unicode en Python 3. Donc gaffe quand vous portez votre code d’une version à l’autre. Les noms de fichiers contiennent des caractères non ASCII dans la vraie vie vivante.

Si vous voulez récupérer uniquement les dossiers ou les fichiers, il va falloir filtrer :

>>> for element in os.listdir('/tmp/test'):
...     if os.path.isdir(element):
...         print("'%s' un dossier" % element)
...     else:
...         print("'%s' est un fichier" % element)
'dossier' un dossier
'Dossier' un dossier
'fichier' est un fichier
'fIchier' est un fichier
'.fichier' est un fichier
'Fichier' est un fichier

Pareil si on veut filtrer par extensions :

>>> for element in os.listdir('/tmp/test/dossier'):
...     if element.endswith('.txt'):
...         print("'%s' est un fichier texte" % element)
...     else:
...         print("'%s' n'est pas un fichier texte" % element)
'fichier.txt' est un fichier texte
'pas_un_dossier.txt' est un fichier texte
'fichier.py' n'est pas un fichier texte

Néanmoins, Python vient avec le module glob qui permet de demander le listing du contenu d’un dossier en appliquant des filtres Unix :

>>> import glob
>>> glob.glob('/tmp/test/dossier/*.txt')
['/tmp/test/dossier/fichier.txt', '/tmp/test/dossier/pas_un_dossier.txt']
>>> glob.glob('./dossier/*.txt')
['./dossier/fichier.txt', './dossier/pas_un_dossier.txt']

Mais comme vous pouvez le voir, le comportement n’est pas le même que listdir : les éléments de la liste sont des chemins relatifs à celui passé en paramètre si il est lui-même relatif, ou absolus si celui passé en paramètre est absolu. N’oubliez donc pas de normaliser l’entrée ou la sortie avec os.path.realpath qui retournera un chemin canonique.

Même problème Python 2/3 : le type est str dans les 2 cas.

Parcours récursif

La fonction os.walk permet de lister récursivement tous les fichiers et les dossiers à partir d’un point dans l’arborescence. C’est un générateur, et sa valeur de retour est un peu particulière et se récupère via unpacking:

for dossier, sous_dossiers, fichiers in os.walk('/tmp/test'):
    print('##### %s #####' % dossier)
    print("Sous dossiers : %s" % sous_dossiers)
    print("Fichiers : %s" % fichiers)
##### /tmp/test #####
Sous dossiers : ['dossier', 'Dossier']
Fichiers : ['fichier', 'fIchier', '.fichier', 'Fichier']
##### /tmp/test/dossier #####
Sous dossiers : []
Fichiers : ['fichier.txt', 'pas_un_dossier.txt', 'fichier.py']
##### /tmp/test/Dossier #####
Sous dossiers : ['dOssier']
Fichiers : ['fichiiiiiiiiier']
##### /tmp/test/Dossier/dOssier #####
Sous dossiers : []
Fichiers : ['faichier']

Du coup, si vous voulez avoir la liste des chemin des fichiers absolus, il faut reconstituer à la main :

for dossier, sous_dossiers, fichiers in os.walk('/tmp/test'):
    for fichier in fichiers:
        print(os.path.join(dossier, fichier))
/tmp/test/fichier
/tmp/test/fIchier
/tmp/test/.fichier
/tmp/test/Fichier
/tmp/test/dossier/fichier.txt
/tmp/test/dossier/pas_un_dossier.txt
/tmp/test/dossier/fichier.py
/tmp/test/Dossier/fichiiiiiiiiier
/tmp/test/Dossier/dOssier/faichier

Encore une fois, c’est du str partout, alors attention. L’article sur l’encoding en Python reste un des plus consultés de la categ prog.

Avec une lib qui va bien

Je n’ai jamais caché mon amour immodéré pour path.py (qui enterre tous ses concurrents, dont unipath), qui encapsule toutes les opérations des fichiers de manière simple et élégante et qui marche sous Python 2 et 3.

pip install path.py

Et c’est quand même plus facile pour le parcours récursif :

>>> from path import path
>>> for f in path('.').walkfiles():
    print f
   ....:     
./dossier/fichier.txt
./dossier/pas_un_dossier.txt
./dossier/fichier.py
./Dossier/fichiiiiiiiiier
./Dossier/dOssier/faichier
./fichier
./fIchier
./.fichier
./Fichier

En plus, f est de type path sous Python 2 et 3. C’est homogène, et ça permet de faire toutes les opérations magiques que ce type permet.

Néanmoins, si vous êtes sous Python 3.4 et que vous ne voulez pas ajouter une dépendance externe, vous pouvez utiliser le module pathlib :

>>> from pathlib import Path
>>> for p in  Path('.').glob('./**/*'):
...    if p.is_file():
...        print(p)
fichier
fIchier
.fichier
Fichier
dossier/fichier.txt
dossier/pas_un_dossier.txt
dossier/fichier.py
Dossier/fichiiiiiiiiier
Dossier/dOssier/faichier

12 thoughts on “Appliquer un traitement à tous les fichiers d’un dossier en Python

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.