Python : lister le contenu de répertoires avec scandir()

Une des tâches de base en informatique, c’est lister le contenu de répertoires puis parcourir une arborescence. Python a été créé dans les années 1990 et le langage intègre donc des fonctions natives. Mais ces fonctions ont des limites. Le langage a évolué et de nouvelles fonctions plus performantes ont été ajoutées.

Cependant, les habitudes ont la vie dur. Les anciens ont appris avec la méthode historique, ont appris à faire avec et ne se sont pas intéressés à une évolution. Puis, ils enseignent cette même version et tout le monde l’utilise encore.

Dans cet article nous allons voir le passage de l’approche historique avec os.listdir() à la fonction plus récente os.scandir().

Avant de commencer, notons que ces deux fonctions sont dans le module os. Pour travailler efficacement avec os.listdir(), nous aurons aussi besoin de fonctions du module os.path.

Les méthodes historiques

La méthode historique pour obtenir le contenu d’un répertoire consiste à utiliser la fonction os.listdir(path)path est une chaine de caractères indiquant un chemin (absolu ou relatif). Cette fonction retourne une liste de chaines de caractères correspondant au contenu du répertoire.

In[2]: import os
In[3]: os.listdir("assets")
Out[3]: 
['Paris',
 'bbts12.csv',
 'movieslist.csv',
 'ile_de_france',
 'showslist_dup.csv',
 'showslist_bad.csv',
 'ministere_culture',
 'showsfiles.txt',
 'files',
 'movieslistdest.csv',
 'showslist.csv',
 'SNCF']

Le contenu est conforme à l’attendu, cependant, vous n’avez aucune information de ce qui est un répertoire ou un fichier. Vous pourriez itérer sur cette liste et tester avec par exemple la fonction os.path.isdir(path) :

In[4]: for filename in os.listdir("assets"):
           if os.path.isdir(filename):
               print(filename, "est un répertoire")
 

Ce qui ne vous affichera rien…

Ce qui (en théorie) n’est pas normal… car dans notre cas, files par exemple est un répertoire.

Pour comprendre ce qui se passe, il faut revoir le comportement de os.path.isdir(path). Cette. fonction retourne True si le paramètre est un chemin qui existe et qui pointe vers un répertoire. Elle retourne donc False si la chaine de caractères n’est pas un chemin vers un répertoire mais également si le chemin n’existe pas. Oui, le piège est que si cette fonction retourne False, vous ne savez pas avec certitude si le chemin existe réellement ou non.

Dans le cas présent, ce qui a été passé en paramètre, c’est justement un chemin qui n’existe pas. Nous avons utilisé le retour de os.listdir(path) qui retourne le contenu du répertoire path. Nous testons donc si le chemin "file" existe dans le répertoire courant.

Si au lieu de tester l’existence du fichier vous voulez ouvrir le fichier avec la fonction open(path), vous auriez dans le cas présent une FileNotFoundError.

Pour que le test soit cohérent, il aurait fallu reconstruire le chemin complet "assets/file" avant de le fournir à la fonction. Le code doit donc être le suivant :

In[5]: for filename in os.listdir("assets"):
           full_filename = os.path.join("assets", filename)

           if os.path.isdir(full_filename):
               print(filename, "est un répertoire")

La fonction os.listdir(path), historique, permet de récupérer le contenu d’un répertoire sous forme de chaines de caractères, mais vous devez faire attention à ce que vous avez récupéré. Pour exploiter ces chaines de caractères, vous devez reconstruire les arborescence, ce qui est relativement lourd.

Et voici scandir

La fonction os.scandir(path) a été ajoutée à la version 3.5 de Python suite à la PEP 471. Cette fonction retourne un générateur d’objets de type DirEntry. Attention, le retour n’est donc plus d’une liste mais un générateur.

Le plus intéressant est le type de données retourné : des objets de type os.DirEntry. Chaque objet représente un élément du répertoire. Ces objets ont deux attributs :

  • name : chaine de caractères représentant le nom du fichier
  • path : chaine de caractères représentant le chemin complet du fichier. Ce chemin est construit en utilisant le paramètre path passé à scandir().

Vous pouvez donc obtenir grâce à ces attributs aussi bien le nom du fichier que son chemin sans avoir à reconstruire ce dernier.

Ces objets ont également des méthodes. Les plus usuelles sont :

  • is_dir() : retourne True si l’objet pointe sur un répertoire
  • is_file() : retourne True si l’objet pointe sur un fichier
  • stat() : retourne un objet de type dictionnaire représentant les métadonnées du fichier tel que la date de création, taille…

Avec scandir(), vous pouvez donc récupérer tout un ensemble d’informations de manière efficace. En effet, ce dernier limite le nombre d’accès disque en chargeant et mettant en cache le maximum d’informations en une seule passe.

Vous pouvez donc écrire, sans vous inquiéter, du code de ce type :

for element in os.scandir("assets"):
    if element.is_file() and element.name.startswith("shows"):
        process_file(element.path)

Nous pouvons vérifier directement si l’élément du répertoire est un fichier et, si oui, si le nom du fichier commence par une chaine de caractères le traiter à partir de son chemin complet.

En conclusion

Les habitudes ont la vie dure en informatique. Quand on a un outil qui fait le travail, pourquoi en changer ? Et pourtant, le bénéfice de os.scandir() par rapport à os.listdir() est indéniable. Si vous êtres habitués à la méthode historique, je vous encourage à vous mettre à jour. Connaitre os.listdir() ne devrait être utile que pour comprendre et faire évoluer du code legacy.

Mais manipuler des fichiers avec des objets de type os.DirEntry ne sont pas non plus les plus pratique. Ça tombe bien, il faudra que je vous parle du module pathlib

Si vous avez aimé ce post, n’hésitez pas à laisser un commentaire ci-dessous ou sur la page Facebook 😉

À propos de... Darko Stankovski

iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.

Vous aimerez aussi...

Laisser un commentaire