Gestion sûre des chemins multiplateformes en Python avec pathlib
Guide pratique pour utiliser pathlib afin de vérifier l'existence de fichiers, manipuler les extensions, naviguer dans les répertoires et résoudre les chemins sans introduire de bugs spécifiques à Windows ou Unix.
Dans ce guide
La réponse courte
pathlib sépare les objets de chemin purs (sans E/S) des objets concrets (appels système), permettant de manipuler des chemins Windows sur Unix en toute sécurité. Protégez toujours l'accès aux fichiers avec try/except autour de open plutôt que de compter uniquement sur exists(), car le système de fichiers peut changer entre la vérification et l'utilisation. Utilisez with_suffix, with_stem et with_name pour modifier les extensions, iterdir et glob pour la traversée, resolve pour la normalisation, et passez directement les objets pathlib aux fonctions os via os.PathLike.
Chemins purs contre chemins concrets : choisir la bonne classe
pathlib divise son API en deux familles. PurePath et ses sous-classes (PurePosixPath, PureWindowsPath) effectuent uniquement des calculs au niveau de la chaîne, tels que la jointure, la division et le remplacement de suffixe, sans émettre aucun appel système. Path, PosixPath et WindowsPath héritent des classes pures et ajoutent des méthodes d'E/S comme open, read_text et mkdir.
L'insight crucial pour la compatibilité multiplateforme est que vous pouvez instancier n'importe quelle saveur pure sur n'importe quel système d'exploitation. Sur une machine Linux, vous ne pouvez pas créer un objet WindowsPath car cela tenterait un appel système spécifique à Windows, mais PureWindowsPath fonctionne partout. Cela vous permet de valider, normaliser ou transformer un chemin UNC Windows dans un environnement de test Unix sans toucher au système de fichiers.
Selon la documentation de pathlib, les chemins purs sont utiles lorsque vous souhaitez manipuler des chemins Windows sur une machine Unix ou lorsque vous devez garantir que votre code n'effectue jamais d'opérations accédant à l'OS. Les chemins concrets doivent être réservés au moment où vous avez réellement besoin d'interagir avec le système de fichiers.
from pathlib import Path, PureWindowsPath, PurePosixPath, UnsupportedOperation, NotImplementedError # noqa: F401 (illustrative imports only)Conseil pratique
Lorsque vous construisez un chemin à partir de segments fournis par l'utilisateur, appelez resolve(strict=False) pour réduire les composants '..' et rendre le chemin absolu, mais ne considérez pas cela comme suffisant : vérifiez également que le chemin résolu se trouve bien dans un répertoire de base autorisé (par exemple, comparez Path(base).resolve() avec os.path.commonpath ou Path.is_relative_to), et rappelez-vous qu'un lien symbolique peut changer entre la résolution et l'écriture, donc revérifiez ou ouvrez immédiatement avec le chemin résolu.
Vérifier l'existence et le type sans conditions de course
exists(), is_file() et is_dir() renvoient des booléens en appelant stat en arrière-plan. Ils sont pratiques pour des diagnostics rapides mais introduisent une condition de course de type time-of-check-to-time-of-use : un autre processus ou thread peut supprimer ou remplacer le fichier entre la vérification et l'ouverture subséquente. La documentation de pathlib note que les méthodes de chemin concret peuvent lever OSError si un appel système échoue.
Le modèle plus sûr consiste à tenter l'opération directement dans un bloc try/except OSError. Si vous devez vérifier d'abord (par exemple, pour décider s'il faut créer ou lire), gardez la fenêtre entre la vérification et l'action aussi petite que possible et enveloppez toujours l'action dans une gestion d'exception.
from pathlib import Path
target = Path('output/report.csv')
# Preferred: attempt and handle failure
try:
content = target.read_text(encoding='utf-8')
except FileNotFoundError:
print(f'{target} does not exist yet')
except PermissionError:
print(f'No permission to read {target}')
except OSError as exc:
print(f'OS error on {target}: {exc}')Modifier les extensions en toute sécurité avec with_suffix, with_name et with_stem
with_suffix remplace le suffixe existant ou en ajoute un s'il n'y en a pas. Passer une chaîne vide supprime entièrement le suffixe. La méthode ne regarde que le dernier segment après le point, donc pour un fichier nommé archive.tar.gz, le suffixe est '.gz' et with_suffix('.bz2') donne archive.tar.bz2, pas archive.bz2.
with_name remplace l'ensemble du dernier composant, y compris tout suffixe. with_stem (ajouté dans Python 3.9) modifie uniquement la partie avant le suffixe, en le préservant. Les deux lèvent ValueError lorsque le chemin n'a pas de composant nom, comme une racine de lecteur nue telle que C:/.
Une erreur courante consiste à supposer que with_suffix gère les extensions à plusieurs points comme .tar.gz comme une unité. Ce n'est pas le cas ; vous devez utiliser with_name ou une construction manuelle du stem pour les extensions composées.
from pathlib import PureWindowsPath
p = PureWindowsPath('c:/Downloads/archive.tar.gz')
print(p.with_suffix('.bz2')) # c:/Downloads/archive.tar.bz2
print(p.with_stem('backup')) # c:/Downloads/backup.gz
print(p.with_name('new.txt')) # c:/Downloads/new.txtNaviguer dans les arborescences de répertoires avec iterdir et glob
glob accepte des motifs de style shell ; '' signifie ce répertoire et tous les sous-répertoires récursivement, et recursive=True est la valeur par défaut, donc glob('/*.py') recherche dans tout l'arbre sans drapeau explicite. rglob est un raccourci pour glob('**/motif').
Les résultats de iterdir et glob sont renvoyés dans un ordre arbitraire et peuvent inclure des entrées cachées (dotfiles sur Unix, fichiers avec l'attribut caché sur Windows). Si vous avez besoin d'un ordre déterministe, triez explicitement les résultats. Le fait que glob suive les liens symboliques peut varier selon la version de Python, donc vérifiez le comportement par rapport à la documentation de pathlib pour la version que vous utilisez plutôt que de supposer une règle fixe.
from pathlib import Path
data_dir = Path('output')
for p in sorted(data_dir.iterdir()):
if p.is_file() and p.suffix == '.csv':
print(p.resolve())
# Recursive search for all Python files
for py in data_dir.rglob('*.py'):
print(py)Résolution des chemins : absolute, resolve, expanduser et home
absolute() préfixe le répertoire de travail actuel sans normaliser les segments dot-dot ni suivre les liens symboliques. resolve() élimine les composants '..' et suit chaque lien symbolique rencontré, renvoyant un chemin canonique. Dans Python 3.6, le paramètre strict a été ajouté ; avec strict=True, un chemin manquant ou une boucle de lien symbolique lève OSError, tandis que strict=False (par défaut) résout aussi loin que possible et ajoute le reste.
expanduser() remplace une tilde initiale ou une construction tilde-utilisateur par le répertoire personnel correspondant. home() est une méthode de classe qui renvoie directement le chemin du répertoire personnel de l'utilisateur actuel. Les deux lèvent RuntimeError lorsque le répertoire personnel ne peut pas être déterminé.
from pathlib import Path
p = Path('docs/../setup.py')
print(p.resolve()) # /home/user/project/setup.py
q = Path('~/notes.txt')
print(q.expanduser()) # /home/user/notes.txt
print(Path.home()) # /home/userPropriétés multiplateformes : drive, root, parts et sensibilité à la casse
La propriété drive renvoie une lettre de lecteur Windows ou une chaîne de partage UNC, et est toujours vide sur POSIX. root renvoie la barre oblique ou inverse initiale. parts décompose le chemin en un tuple de composants, regroupant drive et root en une seule entrée sur Windows.
PureWindowsPath ignore la casse dans les comparaisons d'égalité et d'ordre, donc PureWindowsPath('FOO') est égal à PureWindowsPath('foo'). PurePosixPath est sensible à la casse. Cette distinction est importante lors de la création d'ensembles ou de dictionnaires de chemins qui doivent se comporter de manière cohérente sur toutes les plateformes.
from pathlib import PureWindowsPath, PurePosixPath
w = PureWindowsPath('C:/Users/alice/docs')
print(w.parts) # ('C:\\', 'Users', 'alice', 'docs')
print(w.drive) # 'c:'
print(w.root) # '\\'
print(PureWindowsPath('FOO') == PureWindowsPath('foo')) # True
print(PurePosixPath('FOO') == PurePosixPath('foo')) # FalseGérer les erreurs de chemin : OSError, UnsupportedOperation et restrictions de plateforme
Les méthodes de chemin concret qui touchent le système de fichiers lèvent OSError (ou des sous-classes telles que FileNotFoundError, PermissionError) lorsque l'appel système sous-jacent échoue. resolve(strict=True) lève OSError pour les chemins inexistants ou les boucles de liens symboliques. À partir de Python 3.13, PosixPath lève UnsupportedOperation lorsqu'il est instancié sur Windows, et WindowsPath le lève sur les plateformes non-Windows. Auparavant, ces cas levaient NotImplementedError.
UnsupportedOperation hérite de NotImplementedError, donc capturer NotImplementedError capturera aussi UnsupportedOperation, mais une gestion explicite est plus claire. Lors de l'écriture de code devant fonctionner sur les deux plateformes, préférez Path à PosixPath ou WindowsPath pour éviter l'instanciation accidentelle de la mauvaise saveur.
from pathlib import Path
try:
Path('/nonexistent').resolve(strict=True)
except OSError as exc:
print(f'Resolution failed: {exc}')
# On Python 3.13+, this raises UnsupportedOperation on Linux:
# from pathlib import WindowsPath
# WindowsPath('C:/')Intégration des objets pathlib avec les fonctions du module os
PurePath implémente os.PathLike depuis Python 3.6, donc tout objet pathlib peut être passé directement à os.listdir, os.stat, os.remove, os.symlink et des fonctions similaires sans conversion. Cela vous permet de mélanger la commodité de pathlib avec des opérations de niveau os qui n'ont pas d'équivalent pathlib.
os.name renvoie 'posix' ou 'nt' et est la méthode standard pour faire une branche selon la plateforme lorsque pathlib seul n'expose pas assez d'informations, comme vérifier si os.symlink nécessite des privilèges élevés sur Windows ou si une fonction prend en charge le paramètre dir_fd.
import os
from pathlib import Path
p = Path('/tmp/example')
# pathlib object works directly with os functions
os.makedirs(p, exist_ok=True)
os.remove(p / 'old.txt')
if os.name == 'nt':
print('Windows: symlinks need Developer Mode')
else:
print('Unix: symlinks available without elevation')Points à vérifier
- PureWindowsPath peut être instancié sur Linux sans lever d'erreur, confirmant que les chemins purs n'effectuent aucun appel système
- with_suffix('.bz2') sur archive.tar.gz produit archive.tar.bz2, pas archive.bz2, car seul le dernier segment après le point est traité comme le suffixe
- with_name sur un chemin sans composant nom (par ex. PureWindowsPath('c:/')) lève ValueError
- resolve(strict=True) lève OSError lorsque le chemin n'existe pas, tandis que strict=False par défaut résout partiellement
- PureWindowsPath('FOO') == PureWindowsPath('foo') évalue à True alors que la même comparaison avec PurePosixPath est False
- os.listdir accepte un objet pathlib Path directement grâce au support os.PathLike depuis Python 3.6
- Sur Python 3.13, PosixPath instancié sur Windows lève UnsupportedOperation plutôt que NotImplementedError
- iterdir et glob renvoient des résultats dans un ordre arbitraire et peuvent inclure des entrées cachées selon la plateforme
Champ d’application
Ce guide couvre uniquement la bibliothèque standard pathlib et n'aborde pas les bibliothèques de chemins tierces telles que pydantic ou fsspec. La création de liens symboliques sur Windows nécessite le Mode Développeur ou des privilèges administrateur ; le guide note cette contrainte mais ne fournit pas de solution de contournement. L'intégration os.PathLike est montrée uniquement pour les fonctions os courantes ; les extensions spécifiques à la plateforme comme os.setxattr sont hors du champ d'application. Le source du module json n'a pas été utilisé car il ne contribue pas aux conseils de manipulation de chemins.