Manipuler des fichiers sur votre système d'exploitation avec le module pathlib

Découvrez le module pathlib, disponible depuis Python 3.4, qui permet d'effectuer presque toutes les opérations courantes sur un système d'exploitation.

Publié le par Thibault Houdon (mis à jour le )
paceTemps de lecture estimé : 33 minutes

La gestion des chemins de fichiers avec Python, c'est toute une histoire !

Python est excellent pour créer des scripts qui ont pour vocation de gérer un système de fichiers et de dossiers.

J'ai personnellement une bonne dizaine de scripts qui tournent tous les jours sur ma machine pour différentes tâches de classification et d'organisation de mon système d'exploitation.

Historiquement, on utilisait des modules comme le module os, glob ou encore shutil pour exécuter des opérations de création, suppression et gestion des fichiers.

Le module pathlib, disponible depuis la version 3.4 de Python, permet d'effectuer presque toutes les opérations courantes sur un système d'exploitation, avec une syntaxe orientée objet beaucoup plus agréable à utiliser.

Elle est désormais utilisée par de plus en plus de librairie en remplacement du module os.

La classe Path

La classe Path permet de créer un objet représentant un chemin vers un fichier ou un dossier de notre ordinateur. Ce chemin peut exister ou ne pas exister sur notre disque dur, ce n'est pas un prérequis.

La classe Path dispose de plusieurs méthodes de classes permettant d'accéder à des chemins courants de notre système d'exploitation, comme le dossier utilisateur :

from pathlib import Path

dossier_utilisateur = Path.home()

On peut également récupérer le dossier courant :

from pathlib import Path

dossier_courant = Path.cwd()

Ou encore créer un chemin spécifique en passant une chaîne de caractères à la classe Path :

from pathlib import Path

documents = Path("/Users/thibh/Documents")

Si on affiche l'objet créé à partir de la classe Path, on se retrouve avec un objet PosixPath.

Cet objet représente les chemins des systèmes Linux et Mac OS. Sur Windows, l'objet sera différent, car les chemins sur Windows ne sont pas les mêmes que sur Mac et Linux.

Cela ne change cependant rien aux méthodes et attributs que l'on peut utiliser sur cet objet.

>>> from pathlib import Path
>>> documents = Path("/Users/thibh/Documents")
>>> print(documents)
PosixPath('/Users/thibh/Documents')

Concaténer des chemins

Pour concaténer des chemins, c'est très simple, il suffit d'utiliser une barre oblique (un slash en bon franglais) :

from pathlib import Path

home = Path.home()  # PosixPath('/Users/thibh/')
documents = home / "Documents"  # PosixPath('/Users/thibh/Documents')

Le résultat de cette concaténation nous retourne un nouvel objet PosixPath, nous pouvons donc concaténer plusieurs chaînes de caractères à la suite.

pathlib se charge de son côté de gérer les différents systèmes d'exploitation et d'utiliser un slash ou un antislash selon que vous utilisiez Mac / Linux ou Windows.

Ce comportement est donc similaire à la fonction os.path.join du module os.

from pathlib import Path

home = Path.home()  # PosixPath('/Users/thibh/')
documents = home / "Documents" / "source_code"  # PosixPath('/Users/thibh/Documents/source_code')

On peut également utiliser la méthode joinpath sur un objet Path.

Ça peut être pratique si vous avez par exemple une liste de dossiers que vous souhaitez concaténer (grâce à l'unpacking et à l'opérateur splat *) :

from pathlib import Path

home = Path.home()  # PosixPath('/Users/thibh/')
dossiers = ['Projets', 'Django', 'blog']
home.joinpath(*dossiers)  # PosixPath('/Users/thibh/Projets/Django/blog')

Si vous utilisez des slashs et que vous souhaitez utiliser une méthode de l'objet Path, pensez à utiliser les parenthèses pour entourer les chemins concaténés :

from pathlib import Path

home = Path.home() # PosixPath('/Users/thibh/')

# Ne fonctionne pas car on essaie de récupérer l'attribut suffix sur la chaîne de caractères "main.py"
home / "Projet" / "main.py".suffix  

# Avec des parenthèses, ça fonctionne !
(home / "Projet" / "main.py").suffix

Récupérer des informations sur un chemin

Grâce à l'orienté objet, on peut accéder à de nombreuses informations sur un chemin avec les attributs de l'objet Path :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.name    # "index.html"
p.parent  # "/Users/thibh/Documents"
p.stem    # "index"
p.suffix  # ".html"
p.parts   # ("/", "Users", "thibh", "documents", "index.html")

Il existe également des méthodes qui permettent de vérifier l'existence et le type d'un chemin :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.exists()   # True
p.is_dir()   # False
p.is_file()  # True

Là encore, quand un chemin peut nous être retourné par un de ces attributs, on récupère un objet Path.

On peut donc mettre bout à bout plusieurs fois le même attribut pour remonter de plusieurs dossiers par exemple :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.parent.parent  # "/Users/thibh"

Créer et supprimer des dossiers

Pour créer un dossier, on peut utiliser la méthode mkdir. Cette méthode ne fonctionnera par défaut que si le dossier n'existe pas.

Vous pouvez utiliser le paramètre exist_ok pour signifier que vous ne souhaitez pas qu'une erreur soit levée si le dossier existe déjà :

from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
dossier.mkdir()  # Lève une erreur si le dossier existe déjà
dossier.mkdir(exist_ok=True)

Si vous souhaitez créer une hiérarchie de dossier qui n'existent pas, il faut ajouter le paramètre parents :

from pathlib import Path

# Le dossier SiteWeb et ses sous-dossiers n'existent pas
dossier = Path("/Users/thibh/Documents/SiteWeb/sources/css")

# On peut tout créer d'un coup avec le paramètre parents
dossier.mkdir(parents=True)

Pour supprimer un dossier, on utilise la méthode rmdir :

from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
dossier.rmdir()

Cette méthode ne fonctionne que si le dossier est vide.

Si le dossier contient des fichiers ou d'autres sous-dossiers, cette méthode ne fonctionnera pas, et c'est le seul cas de figure où nous sommes obligés de repasser par le module shutil et la fonction rmtree :

import shutil
from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
shutil.rmtree(dossier)

Créer, lire et écrire dans un fichier

Pour créer et supprimer un fichier, on peut utiliser respectivement les méthodes touch et unlink :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
fichier.touch()   # On crée le fichier
fichier.unlink()  # On supprime le fichier

Pour écrire du contenu dans un fichier, on utilise la méthode write_text :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
fichier.write_text("Accueil du site")

Il n'est pas obligatoire de créer le fichier au préalable avec la méthode touch.

Vous conviendrez que c'est plus rapide que de faire :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
with open(fichier, "w") as f:
    f.write("Accueil du site")

De la même façon, pour lire le contenu d'un fichier, on peut utiliser la méthode read_text :

>>> from pathlib import Path

>>> fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
>>> fichier.read_text()
"Accueil du site"

Pour plus de détails sur ces deux méthodes, vous pouvez continuer avec cette vidéo.

Scanner un dossier

Là où pathlib rayonne, c'est vraiment dans la possibilité de scanner les dossiers de votre ordinateur avec des méthodes beaucoup plus faciles à retenir que le module glob.

Pour récupérer tous les fichiers et dossiers à l'intérieur d'un dossier, on peut utiliser la méthode iterdir :

from pathlib import Path

for f in Path.home().iterdir():
    print(f.name)

On peut combiner cette méthode avec la méthode is_dir pour ne récupérer que les dossiers (ici avec une compréhension de liste) :

from pathlib import Path

dossiers = [d for d in Path.home().iterdir() if d.is_dir()]

Pour scanner un dossier avec un peu plus de flexibilité, on peut utiliser la méthode glob (et oui, comme le module !). On peut par exemple ne récupérer que les fichiers dont l'extension est .png :

from pathlib import Path

for f in Path.home().glob("*.png"):
    print(f.name)

Si vous souhaitez scanner un dossier de façon récursive, il suffit d'utiliser rglob à la place de glob :

from pathlib import Path

for f in Path.home().rglob("*.png"):
    print(f.name)

Facile non 🥳 ?

Pour voir ces méthodes en pratique, n'hésitez pas à jeter un coup d'oeil à cette vidéo.

Nouveautés de la version 3.11

Depuis la version 3.11 de Python, vous pouvez désormais indiquer directement si vous souhaitez récupérer uniquement les dossiers à l'intérieur d'un dossier.

Il suffit pour cela d'ajouter un slash à la fin du chemin : dossier.glob("*/")

J'en parle plus en détail dans cet article sur les nouveautés de Python 3.11.

Quelques cas pratiques

Voici quelques cas pratiques qui vous montrent la flexibilité et la facilité d'utilisation de pathlib.

Ajouter un suffixe à un nom de fichier

from pathlib import Path

p = Path.home() / "image.png"  # "/Users/thibh/image.png"
p.parent / (p.stem + "-lowres" + p.suffix)  # "/Users/thibh/image-lowres.png"

Trier des fichiers selon leur extension

from pathlib import Path

dirs = {".jpg": "Images",
        ".gif": "Images",
        ".mp4": "Videos",
        ".pdf": "Documents",
        ".mp3": "Musiques",
        ".wav": "Musiques"}

tri_dir = Path.home() / "Tri"
files = [f for f in tri_dir.iterdirs() if f.is_file()]
for f in files:
    # Si aucune correspondance n'est trouvé pour l'extension, on place les fichiers dans un dossier Autres
    output_dir = tri_dir / dirs.get(f.suffix, "Autres")
    output_dir.mkdir(exist_ok=True)
    f.rename(output_dir / f.name)

On peut utiliser la méthode rename pour déplacer un fichier.

Créer les constantes d'un dossier avec file

from pathlib import Path

SOURCE_FILE = Path(__file__).resolve()  # resolve permet de résoudre les liens symboliques
SOURCE_DIR = SOURCE_FILE.parent
ROOT_DIR = SOURCE_DIR.parent
DATA_DIR = SOURCE_DIR / "DATA"

J'espère qu'après cet article, vous voyez l'avantage d'utiliser pathlib pour tout ce qui concerne la gestion des fichiers et des dossiers sur votre système d'exploitation.

Il reste important de connaître les modules os, shutil et glob, car il y a encore beaucoup de scripts que vous trouverez sur internet qui en font l'usage.

Mais si vous démarrez un nouveau projet avec Python 3.4+ alors n'hésitez pas une seconde !