Skip to content

Python Pathlib : Le Guide Moderne de la Manipulation des Chemins de Fichiers

Updated on

Si vous avez déjà écrit du code Python comme os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'output'), vous connaissez déjà le problème. La manipulation de chemins de fichiers basée sur des chaînes avec os.path est verbeuse, difficile à lire et source d'erreurs. Vous concaténez des chaînes et oubliez les séparateurs. Vous codez en dur des / et le script plante sur Windows. Vous enchaînez cinq appels os.path juste pour obtenir le nom stem d'un fichier, et trois mois plus tard personne ne peut lire le code — y compris vous.

Ce ne sont pas des cas marginaux. Chaque pipeline de data science, chaque application web et chaque script d'automatisation touche au système de fichiers. Les chemins qui fonctionnent sur votre Mac échouent sur l'ordinateur Windows d'un collègue. Les variables de chemins temporaires s'accumulent dans votre code comme une dette technique. Et plus vous imbriquez d'appels os.path.join(os.path.dirname(...)), plus vous risquez d'introduire un bug subtil qui n'apparaît qu'en production.

Le module pathlib de Python résout cela. Introduit dans Python 3.4 et pleinement mature depuis Python 3.6, pathlib remplace la manipulation de chemins basée sur des chaînes par de véritables objets Path. Les chemins se joignent avec l'opérateur /. Les attributs de fichiers comme .name, .suffix et .stem sont des propriétés, pas des appels de fonction. La lecture et l'écriture de fichiers se font en une ligne. Et tout fonctionne de manière identique sur tous les systèmes d'exploitation. Ce guide couvre toutes les fonctionnalités essentielles de pathlib, de la construction de chemins de base aux patterns avancés pour les workflows de data science.

📚

Pourquoi pathlib plutôt que os.path

Avant pathlib, les développeurs Python comptaient sur os.path pour les opérations de chemins et os pour les interactions avec le système de fichiers. Cette approche fonctionne, mais elle traite les chemins comme de simples chaînes. Cela crée trois problèmes persistants :

  1. La lisibilité se dégrade rapidement. Comparez os.path.splitext(os.path.basename(filepath))[0] avec Path(filepath).stem. Les deux extraient le nom de fichier sans son extension. L'un est auto-documenté ; l'autre nécessite un effort mental d'analyse.

  2. Les bugs multiplateformes. Le codage en dur de / comme séparateur ou l'utilisation de la concaténation de chaînes signifie que votre script Linux casse silencieusement sur Windows. os.path.join aide, mais oublier de l'utiliser ne serait-ce qu'une fois crée un bug latent.

  3. Fonctionnalités éparpillées. Pour travailler avec des chemins, vous avez besoin de os.path pour la décomposition, os pour la création de répertoires, glob pour la correspondance de motifs et open() pour les E/S de fichiers. pathlib consolide tout cela dans un seul objet Path.

Voici la même tâche — trouver tous les fichiers .csv dans un répertoire de données et lire le premier — dans les deux styles :

# Approche os.path
import os
import glob
 
data_dir = os.path.join(os.path.expanduser('~'), 'projects', 'data')
csv_files = glob.glob(os.path.join(data_dir, '**', '*.csv'), recursive=True)
if csv_files:
    with open(csv_files[0], 'r') as f:
        content = f.read()
# Approche pathlib
from pathlib import Path
 
data_dir = Path.home() / 'projects' / 'data'
csv_files = list(data_dir.rglob('*.csv'))
if csv_files:
    content = csv_files[0].read_text()

La version pathlib est plus courte, plus facile à lire, et fait exactement la même chose. Pas d'imports au-delà de Path. Pas de concaténation de chaînes. Pas d'appel open() séparé.

Création des Objets Path

Chaque opération pathlib commence par la création d'un objet Path. La classe Path retourne automatiquement un PosixPath sur Linux/macOS ou un WindowsPath sur Windows.

from pathlib import Path
 
# À partir d'une chaîne
p = Path('/home/user/documents/report.csv')
 
# À partir de plusieurs segments (joints automatiquement)
p = Path('home', 'user', 'documents', 'report.csv')
 
# Répertoire de travail actuel
cwd = Path.cwd()
print(cwd)  # ex., /home/user/projects/myapp
 
# Répertoire personnel de l'utilisateur
home = Path.home()
print(home)  # ex., /home/user
 
# Chemin relatif
p = Path('data/output/results.csv')
 
# À partir d'un chemin existant
base = Path('/home/user')
full = Path(base, 'documents', 'file.txt')
print(full)  # /home/user/documents/file.txt

Path() sans arguments retourne Path('.'), un chemin relatif vers le répertoire actuel. Utilisez Path.cwd() quand vous avez besoin du répertoire actuel absolu.

Joindre les Chemins avec l'Opérateur /

La caractéristique la plus distinctive de pathlib est son opérateur / surchargé. Au lieu de os.path.join(), vous enchaînez les segments de chemin avec / :

from pathlib import Path
 
# Construire des chemins naturellement
project = Path.home() / 'projects' / 'analysis'
data_file = project / 'data' / 'sales_2026.csv'
print(data_file)  # /home/user/projects/analysis/data/sales_2026.csv
 
# Mélanger objets Path et chaînes
base = Path('/var/log')
app_log = base / 'myapp' / 'error.log'
print(app_log)  # /var/log/myapp/error.log
 
# Combiner avec des variables
filename = 'report.pdf'
output = Path('output') / filename
print(output)  # output/report.pdf

L'opérateur / gère automatiquement les séparateurs. Sur Windows, Path('C:/Users') / 'data' produit C:\Users\data. Vous n'avez plus jamais à penser à / vs \.

Vous pouvez aussi utiliser joinpath() pour le même résultat :

from pathlib import Path
 
# Équivalent à Path('data') / 'raw' / 'file.csv'
p = Path('data').joinpath('raw', 'file.csv')
print(p)  # data/raw/file.csv

Composants de Chemin

Chaque objet Path expose ses composants comme des propriétés. Pas d'appels de fonction, pas de découpage de chaînes.

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/sales_report.final.csv')
 
print(p.name)       # sales_report.final.csv  (nom de fichier avec extension)
print(p.stem)       # sales_report.final      (nom de fichier sans dernière extension)
print(p.suffix)     # .csv                    (dernière extension)
print(p.suffixes)   # ['.final', '.csv']      (toutes les extensions)
print(p.parent)     # /home/user/projects/analysis/data
print(p.anchor)     # /                       (racine sur Unix, C:\ sur Windows)
print(p.parts)      # ('/', 'home', 'user', 'projects', 'analysis', 'data', 'sales_report.final.csv')

Navigation vers les Parents

La propriété .parent retourne le répertoire parent immédiat. Enchaînez-la pour monter plus haut :

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/output.csv')
 
print(p.parent)            # /home/user/projects/analysis/data
print(p.parent.parent)     # /home/user/projects/analysis
print(p.parent.parent.parent)  # /home/user/projects
 
# .parents donne un accès indexé à tous les ancêtres
print(p.parents[0])  # /home/user/projects/analysis/data
print(p.parents[1])  # /home/user/projects/analysis
print(p.parents[2])  # /home/user/projects
print(p.parents[3])  # /home/user

Modification des Composants de Chemin

Utilisez .with_name(), .with_stem(), et .with_suffix() pour créer de nouveaux chemins avec des composants modifiés :

from pathlib import Path
 
p = Path('/data/reports/sales_q1.csv')
 
# Changer entièrement le nom de fichier
print(p.with_name('revenue_q1.csv'))    # /data/reports/revenue_q1.csv
 
# Changer uniquement le stem (Python 3.9+)
print(p.with_stem('sales_q2'))          # /data/reports/sales_q2.csv
 
# Changer uniquement l'extension
print(p.with_suffix('.parquet'))        # /data/reports/sales_q1.parquet
 
# Supprimer l'extension
print(p.with_suffix(''))                # /data/reports/sales_q1
 
# Ajouter une extension
backup = p.with_suffix(p.suffix + '.bak')
print(backup)                           # /data/reports/sales_q1.csv.bak

Ces méthodes retournent de nouveaux objets Path. Elles ne renomment pas les fichiers sur le disque.

E/S de Fichiers : Lecture et Écriture

pathlib élimine le boilerplate open() / with pour les opérations simples de fichiers :

from pathlib import Path
 
file_path = Path('example.txt')
 
# Écrire du texte dans un fichier (crée si n'existe pas, écrase si existe)
file_path.write_text('Hello, pathlib!\nSecond line.')
 
# Lire du texte depuis un fichier
content = file_path.read_text()
print(content)
# Hello, pathlib!
# Second line.
 
# Écrire des bytes
binary_path = Path('data.bin')
binary_path.write_bytes(b'\x00\x01\x02\x03')
 
# Lire des bytes
raw = binary_path.read_bytes()
print(raw)  # b'\x00\x01\x02\x03'

Spécifiez explicitement l'encodage lorsque vous travaillez avec du texte non-ASCII :

from pathlib import Path
 
# Écrire du texte UTF-8
Path('greeting.txt').write_text('こんにちは世界', encoding='utf-8')
 
# Lire avec encodage
text = Path('greeting.txt').read_text(encoding='utf-8')
print(text)  # こんにちは世界

Pour les fichiers volumineux ou les opérations en streaming, utilisez .open() qui retourne un gestionnaire de fichier comme le open() intégré :

from pathlib import Path
 
log_file = Path('application.log')
 
# Écrire ligne par ligne
with log_file.open('w') as f:
    for i in range(1000):
        f.write(f'Event {i}: processed\n')
 
# Lire ligne par ligne (efficace en mémoire pour les gros fichiers)
with log_file.open('r') as f:
    for line in f:
        if 'error' in line.lower():
            print(line.strip())

Opérations sur les Répertoires

Création de Répertoires

from pathlib import Path
 
# Créer un répertoire simple
Path('output').mkdir()
 
# Créer avec les parents (comme os.makedirs)
Path('data/raw/2026/february').mkdir(parents=True, exist_ok=True)
 
# parents=True crée tous les répertoires parents manquants
# exist_ok=True empêche l'erreur si le répertoire existe déjà

Une erreur courante est d'oublier parents=True. Sans cela, mkdir() lève une FileNotFoundError si un répertoire parent est manquant. Utilisez toujours parents=True lors de la création de répertoires imbriqués, et exist_ok=True pour rendre l'opération idempotente.

Lister le Contenu d'un Répertoire

from pathlib import Path
 
project = Path('.')
 
# Lister toutes les entrées (fichiers et répertoires)
for entry in project.iterdir():
    print(entry.name, '(dir)' if entry.is_dir() else '(file)')
 
# Filtrer uniquement les fichiers
files = [f for f in project.iterdir() if f.is_file()]
print(f"Found {len(files)} files")
 
# Filtrer uniquement les répertoires
dirs = [d for d in project.iterdir() if d.is_dir()]
print(f"Found {len(dirs)} directories")
 
# Trier par nom
for entry in sorted(project.iterdir()):
    print(entry.name)

Suppression de Répertoires et Fichiers

from pathlib import Path
 
# Supprimer un fichier
Path('temp_output.csv').unlink()
 
# Supprimer un fichier seulement s'il existe (Python 3.8+)
Path('temp_output.csv').unlink(missing_ok=True)
 
# Supprimer un répertoire vide
Path('empty_dir').rmdir()

rmdir() ne supprime que les répertoires vides. Pour les répertoires non vides, utilisez shutil.rmtree() :

from pathlib import Path
import shutil
 
target = Path('data/old_output')
if target.exists():
    shutil.rmtree(target)

Motifs Glob : Recherche de Fichiers

pathlib a un support glob intégré. Pas besoin d'importer le module glob séparément.

Glob Basique

from pathlib import Path
 
project = Path('/home/user/project')
 
# Trouver tous les fichiers Python dans un répertoire
for py_file in project.glob('*.py'):
    print(py_file.name)
 
# Trouver tous les fichiers CSV
csv_files = list(project.glob('*.csv'))
print(f"Found {len(csv_files)} CSV files")
 
# Trouver les fichiers correspondant à un motif
reports = list(project.glob('report_*.xlsx'))

Glob Récursif avec rglob

rglob() recherche récursivement dans tous les sous-répertoires. C'est équivalent à glob('**/*.pattern') mais plus pratique :

from pathlib import Path
 
project = Path('/home/user/project')
 
# Trouver tous les fichiers Python dans tous les sous-répertoires
all_py = list(project.rglob('*.py'))
print(f"Found {len(all_py)} Python files across all directories")
 
# Trouver tous les notebooks Jupyter récursivement
notebooks = list(project.rglob('*.ipynb'))
for nb in notebooks:
    print(f"  {nb.relative_to(project)}")
 
# Trouver tous les fichiers image
images = list(project.rglob('*.png')) + list(project.rglob('*.jpg'))
 
# Trouver tous les fichiers (sans filtre)
all_files = [f for f in project.rglob('*') if f.is_file()]

Motifs Glob Avancés

from pathlib import Path
 
data = Path('data')
 
# Joker caractère unique
data.glob('file_?.csv')        # file_1.csv, file_a.csv
 
# Plages de caractères
data.glob('report_202[456].csv')  # report_2024.csv, report_2025.csv, report_2026.csv
 
# N'importe quel niveau de sous-répertoire
data.glob('**/output/*.csv')   # data/raw/output/result.csv, data/processed/output/result.csv
 
# Extensions multiples (combiner deux globs)
from itertools import chain
all_data = chain(data.rglob('*.csv'), data.rglob('*.parquet'))

Vérification des Chemins

pathlib fournit des méthodes booléennes claires pour vérifier le statut des chemins :

from pathlib import Path
 
p = Path('/home/user/projects/data.csv')
 
# Le chemin existe-t-il ?
print(p.exists())       # True ou False
 
# Est-ce un fichier ?
print(p.is_file())      # True si existe et est un fichier régulier
 
# Est-ce un répertoire ?
print(p.is_dir())       # True si existe et est un répertoire
 
# Est-ce un lien symbolique ?
print(p.is_symlink())   # True si existe et est un symlink
 
# Est-ce un chemin absolu ?
print(p.is_absolute())  # True (/home/... commence par la racine)
print(Path('data.csv').is_absolute())  # False (chemin relatif)

Ces méthodes ne lèvent jamais d'exceptions pour les chemins inexistants. Elles retournent simplement False, ce qui les rend sûres à utiliser dans des conditions :

from pathlib import Path
 
config = Path('config.yaml')
if config.is_file():
    settings = config.read_text()
else:
    print("Config file not found, using defaults")

Manipulation de Chemins

Résolution et Normalisation des Chemins

from pathlib import Path
 
# Résoudre en chemin absolu (résout aussi les liens symboliques)
p = Path('data/../data/./output.csv')
print(p.resolve())  # /home/user/project/data/output.csv
 
# Obtenir le chemin absolu sans résoudre les liens symboliques
print(p.absolute())  # /home/user/project/data/../data/./output.csv
 
# Développer le répertoire personnel de l'utilisateur
p = Path('~/Documents/report.csv')
print(p.expanduser())  # /home/user/Documents/report.csv

Chemins Relatifs

from pathlib import Path
 
full_path = Path('/home/user/projects/analysis/data/output.csv')
base = Path('/home/user/projects')
 
# Obtenir le chemin relatif de base à full_path
relative = full_path.relative_to(base)
print(relative)  # analysis/data/output.csv
 
# Cela lève ValueError si le chemin n'est pas relatif à la base
try:
    Path('/var/log/app.log').relative_to(base)
except ValueError as e:
    print(e)  # '/var/log/app.log' is not relative to '/home/user/projects'
 
# Python 3.12+ : vérification is_relative_to()
print(full_path.is_relative_to(base))   # True
print(Path('/var/log').is_relative_to(base))  # False

Métadonnées de Fichiers et Stat

from pathlib import Path
from datetime import datetime
 
p = Path('data.csv')
 
# Obtenir les statistiques du fichier
stat = p.stat()
print(f"Size: {stat.st_size} bytes")
print(f"Modified: {datetime.fromtimestamp(stat.st_mtime)}")
print(f"Created: {datetime.fromtimestamp(stat.st_ctime)}")
 
# Commodité : obtenir la taille directement (via stat)
size_mb = p.stat().st_size / (1024 * 1024)
print(f"Size: {size_mb:.2f} MB")
 
# Vérifier si deux chemins pointent vers le même fichier
p1 = Path('/home/user/data.csv')
p2 = Path.home() / 'data.csv'
print(p1.samefile(p2))  # True (s'ils résolvent vers le même fichier)

Renommage et Déplacement de Fichiers

from pathlib import Path
 
# Renommer un fichier (retourne le nouveau Path)
old = Path('report_draft.csv')
new = old.rename('report_final.csv')
print(new)  # report_final.csv
 
# Déplacer vers un répertoire différent
source = Path('output/temp_results.csv')
dest = source.rename(Path('archive') / source.name)
 
# Remplacer un fichier (écrase si la destination existe)
Path('new_data.csv').replace('data.csv')

Note : .rename() écrasera le fichier de destination sur Unix mais peut lever une erreur sur Windows. Utilisez .replace() pour un comportement d'écrasement garanti multiplateforme.

os.path vs pathlib : Comparaison Complète

Voici un tableau de référence mappant chaque opération os.path courante à son équivalent pathlib :

Opérationos.path / ospathlib
Joindre des cheminsos.path.join('a', 'b')Path('a') / 'b'
Répertoire courantos.getcwd()Path.cwd()
Répertoire personnelos.path.expanduser('~')Path.home()
Chemin absoluos.path.abspath(p)Path(p).resolve()
Nom de fichieros.path.basename(p)Path(p).name
Répertoireos.path.dirname(p)Path(p).parent
Extensionos.path.splitext(p)[1]Path(p).suffix
Stem (nom sans ext)os.path.splitext(os.path.basename(p))[0]Path(p).stem
Existeos.path.exists(p)Path(p).exists()
Est un fichieros.path.isfile(p)Path(p).is_file()
Est un répertoireos.path.isdir(p)Path(p).is_dir()
Est un lien symboliqueos.path.islink(p)Path(p).is_symlink()
Est absoluos.path.isabs(p)Path(p).is_absolute()
Taille du fichieros.path.getsize(p)Path(p).stat().st_size
Lister le répertoireos.listdir(p)Path(p).iterdir()
Créer un répertoireos.makedirs(p, exist_ok=True)Path(p).mkdir(parents=True, exist_ok=True)
Supprimer un fichieros.remove(p)Path(p).unlink()
Supprimer un répertoireos.rmdir(p)Path(p).rmdir()
Renommeros.rename(old, new)Path(old).rename(new)
Lire un fichieropen(p).read()Path(p).read_text()
Écrire un fichieropen(p, 'w').write(text)Path(p).write_text(text)
Globglob.glob('*.py')Path('.').glob('*.py')
Glob récursifglob.glob('**/*.py', recursive=True)Path('.').rglob('*.py')
Développer l'utilisateuros.path.expanduser(p)Path(p).expanduser()
Chemin relatifos.path.relpath(p, base)Path(p).relative_to(base)

Travail avec des Fichiers Temporaires

pathlib s'intègre proprement avec le module tempfile de Python :

from pathlib import Path
import tempfile
 
# Créer un répertoire temporaire comme Path
with tempfile.TemporaryDirectory() as tmp_dir:
    tmp_path = Path(tmp_dir)
 
    # Écrire des fichiers temporaires en utilisant pathlib
    data_file = tmp_path / 'intermediate_results.csv'
    data_file.write_text('col1,col2\n1,2\n3,4\n')
 
    config_file = tmp_path / 'run_config.json'
    config_file.write_text('{"epochs": 100, "lr": 0.001}')
 
    # Lister ce que nous avons créé
    for f in tmp_path.iterdir():
        print(f"{f.name}: {f.stat().st_size} bytes")
 
    # Traiter les fichiers...
    print(data_file.read_text())
 
# Le répertoire et tous les fichiers sont automatiquement supprimés ici
from pathlib import Path
import tempfile
 
# Créer un fichier temporaire nommé
tmp = tempfile.NamedTemporaryFile(suffix='.csv', delete=False)
tmp_path = Path(tmp.name)
tmp.close()
 
# Utiliser pathlib pour y écrire
tmp_path.write_text('id,value\n1,100\n2,200\n')
print(f"Temp file at: {tmp_path}")
 
# Nettoyer quand terminé
tmp_path.unlink()

Pathlib dans les Workflows de Data Science

Les projets de data science impliquent typiquement la lecture de datasets depuis plusieurs répertoires, la création de dossiers de sortie pour les résultats, et la gestion des artefacts d'expérimentation. pathlib rend ces patterns propres et fiables.

Organisation des Répertoires de Projet

from pathlib import Path
 
def setup_experiment(experiment_name):
    """Créer une structure de répertoire d'expérimentation standard."""
    base = Path('experiments') / experiment_name
 
    dirs = ['data/raw', 'data/processed', 'models', 'results/figures', 'results/tables', 'logs']
 
    for d in dirs:
        (base / d).mkdir(parents=True, exist_ok=True)
 
    # Créer un fichier de config
    config = base / 'config.json'
    if not config.exists():
        config.write_text('{"learning_rate": 0.001, "epochs": 50}')
 
    print(f"Experiment directory ready: {base.resolve()}")
    return base
 
project = setup_experiment('sales_forecast_v2')

Lecture de Multiples Fichiers de Données

from pathlib import Path
import pandas as pd
 
data_dir = Path('data/raw')
 
# Lire tous les fichiers CSV dans un seul DataFrame
dfs = []
for csv_file in sorted(data_dir.glob('*.csv')):
    print(f"Loading {csv_file.name}...")
    df = pd.read_csv(csv_file)
    df['source_file'] = csv_file.stem  # Ajouter le nom de fichier source
    dfs.append(df)
 
combined = pd.concat(dfs, ignore_index=True)
print(f"Loaded {len(combined)} rows from {len(dfs)} files")
 
# Sauvegarder dans le répertoire processed
output_path = Path('data/processed') / 'combined_sales.parquet'
output_path.parent.mkdir(parents=True, exist_ok=True)
combined.to_parquet(output_path)

Après avoir chargé vos données CSV avec pathlib, vous pouvez les explorer visuellement avec PyGWalker (opens in a new tab). Il transforme n'importe quel DataFrame Pandas en une interface interactive de type Tableau pour l'exploration de données par glisser-déposer — aucun code supplémentaire requis.

Sauvegarde des Résultats d'Expérimentation

from pathlib import Path
from datetime import datetime
import json
 
def save_results(metrics, experiment_dir):
    """Sauvegarder les métriques d'expérimentation avec horodatage."""
    results_dir = Path(experiment_dir) / 'results'
    results_dir.mkdir(parents=True, exist_ok=True)
 
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    output_file = results_dir / f'metrics_{timestamp}.json'
 
    output_file.write_text(json.dumps(metrics, indent=2))
    print(f"Results saved to {output_file}")
    return output_file
 
# Usage
metrics = {'accuracy': 0.94, 'f1_score': 0.91, 'loss': 0.187}
save_results(metrics, 'experiments/sales_forecast_v2')

Gestion des Chemins de Fichiers dans les Notebooks

Quand vous travaillez dans des notebooks Jupyter, les chemins cassent souvent parce que le répertoire de travail du notebook peut différer de la racine du projet. pathlib rend cela facile à gérer :

from pathlib import Path
 
# Toujours résoudre vers un chemin absolu depuis l'emplacement du notebook
NOTEBOOK_DIR = Path.cwd()
PROJECT_ROOT = NOTEBOOK_DIR.parent  # si le notebook est dans notebooks/
DATA_DIR = PROJECT_ROOT / 'data'
OUTPUT_DIR = PROJECT_ROOT / 'output'
 
# Maintenant tous les chemins sont absolus et fiables
train_data = DATA_DIR / 'train.csv'
print(f"Loading: {train_data}")
assert train_data.exists(), f"Missing: {train_data}"

Si vous travaillez beaucoup dans Jupyter et souhaitez un environnement alimenté par l'IA qui aide à gérer les fichiers de projet et les chemins de données, RunCell (opens in a new tab) ajoute une couche d'agent IA à votre notebook. Décrivez ce dont vous avez besoin — "trouver tous les fichiers Parquet dans le répertoire de données et charger le plus récent" — et il génère le code pathlib et l'exécute pour vous.

Patterns et Recettes Courants

Écriture Sécurisée de Fichier avec Remplacement Atomique

Prévenez la corruption des données en écrivant d'abord dans un fichier temporaire, puis en remplaçant atomiquement la cible :

from pathlib import Path
import tempfile
 
def safe_write(target_path, content):
    """Écrire du contenu dans un fichier atomiquement pour prévenir la corruption."""
    target = Path(target_path)
    target.parent.mkdir(parents=True, exist_ok=True)
 
    # Écrire dans un fichier temporaire dans le même répertoire
    tmp = tempfile.NamedTemporaryFile(
        mode='w', dir=target.parent, suffix='.tmp', delete=False
    )
    tmp_path = Path(tmp.name)
    try:
        tmp.write(content)
        tmp.close()
        tmp_path.replace(target)  # Atomique sur la plupart des systèmes de fichiers
    except Exception:
        tmp_path.unlink(missing_ok=True)
        raise
 
safe_write('config/settings.json', '{"debug": true}')

Renommage de Fichiers par Lot

from pathlib import Path
 
photos_dir = Path('photos')
 
# Renommer tous les fichiers .jpeg en .jpg
for f in photos_dir.glob('*.jpeg'):
    f.rename(f.with_suffix('.jpg'))
 
# Ajouter un préfixe à tous les fichiers
for i, f in enumerate(sorted(photos_dir.glob('*.jpg')), start=1):
    new_name = f.parent / f'photo_{i:04d}{f.suffix}'
    f.rename(new_name)

Recherche de Fichiers Dupliqués par Taille

from pathlib import Path
from collections import defaultdict
 
def find_potential_duplicates(directory):
    """Trouver les fichiers avec des tailles identiques (duplicatas potentiels)."""
    size_map = defaultdict(list)
 
    for f in Path(directory).rglob('*'):
        if f.is_file():
            size_map[f.stat().st_size].append(f)
 
    # Retourner uniquement les groupes avec plus d'un fichier
    return {size: files for size, files in size_map.items() if len(files) > 1}
 
dupes = find_potential_duplicates('data')
for size, files in dupes.items():
    print(f"\n{size} bytes:")
    for f in files:
        print(f"  {f}")

Construction d'une Visualisation d'Arbre de Fichiers

from pathlib import Path
 
def tree(directory, prefix='', max_depth=3, _depth=0):
    """Afficher une structure d'arbre d'un répertoire."""
    if _depth >= max_depth:
        return
 
    path = Path(directory)
    entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
 
    for i, entry in enumerate(entries):
        is_last = (i == len(entries) - 1)
        connector = '└── ' if is_last else '├── '
        print(f'{prefix}{connector}{entry.name}')
 
        if entry.is_dir():
            extension = '    ' if is_last else '│   '
            tree(entry, prefix + extension, max_depth, _depth + 1)
 
tree('my_project', max_depth=3)

Sortie :

├── data
│   ├── processed
│   │   └── combined.csv
│   └── raw
│       ├── sales_2025.csv
│       └── sales_2026.csv
├── notebooks
│   └── analysis.ipynb
├── output
│   └── figures
└── requirements.txt

Erreurs Courantes et Comment les Éviter

Erreur 1 : Comparer des Chaînes à des Objets Path

from pathlib import Path
 
p = Path('data/output.csv')
 
# FAUX : Comparer une chaîne à un Path
if p == 'data/output.csv':  # Peut marcher mais fragile
    print("Match")
 
# CORRECT : Comparer Path à Path, ou utiliser str()
if p == Path('data/output.csv'):
    print("Match")
 
# CORRECT : Convertir en chaîne si nécessaire
if str(p) == 'data/output.csv':
    print("Match")

Erreur 2 : Oublier parents=True dans mkdir

from pathlib import Path
 
# FAUX : Lève FileNotFoundError si 'data' n'existe pas
# Path('data/raw/2026').mkdir()
 
# CORRECT : Créer tous les parents manquants
Path('data/raw/2026').mkdir(parents=True, exist_ok=True)

Erreur 3 : Utiliser la Concaténation de Chaînes au lieu de /

from pathlib import Path
 
base = Path('/home/user')
 
# FAUX : La concaténation de chaînes casse pathlib
# bad = base + '/data/file.csv'  # TypeError
 
# CORRECT : Utiliser l'opérateur /
good = base / 'data' / 'file.csv'

Erreur 4 : Passer des Path à des Bibliothèques qui Attendent des Chaînes

La plupart des bibliothèques modernes (Pandas, NumPy, PIL, etc.) acceptent les objets Path nativement. Mais si vous rencontrez une bibliothèque plus ancienne qui nécessite des chaînes, convertissez explicitement :

from pathlib import Path
 
p = Path('data/output.csv')
 
# La plupart des bibliothèques acceptent Path directement
import pandas as pd
df = pd.read_csv(p)  # Fonctionne bien
 
# Pour les bibliothèques plus anciennes qui ont besoin de chaînes
import some_legacy_lib
some_legacy_lib.process(str(p))  # Convertir avec str()
 
# os.fspath() fonctionne aussi (Python 3.6+)
import os
some_legacy_lib.process(os.fspath(p))

Erreur 5 : Utiliser des Chemins Codés en Dur

from pathlib import Path
 
# FAUX : Chemin absolu codé en dur
# data_path = Path('/home/alice/project/data/sales.csv')
 
# CORRECT : Construire à partir de composants relatifs ou dynamiques
data_path = Path.cwd() / 'data' / 'sales.csv'
 
# CORRECT : Construire à partir du répertoire personnel
config_path = Path.home() / '.config' / 'myapp' / 'settings.json'
 
# CORRECT : Construire à partir d'une variable d'environnement
import os
data_root = Path(os.getenv('DATA_DIR', 'data'))
data_path = data_root / 'sales.csv'

Questions Fréquemment Posées

Qu'est-ce que pathlib en Python ?

pathlib est un module de la bibliothèque standard (introduit dans Python 3.4) qui fournit des classes orientées objet pour travailler avec les chemins du système de fichiers. Au lieu de traiter les chemins comme des chaînes et d'utiliser des fonctions comme os.path.join(), vous créez des objets Path et utilisez des méthodes et des opérateurs. Il gère automatiquement les différences de chemins entre les systèmes d'exploitation.

Quand devrais-je utiliser pathlib au lieu de os.path ?

Utilisez pathlib pour tous les nouveaux projets Python 3.6+. Il produit un code plus propre et plus lisible, consolide les opérations de chemin dans un seul objet, et gère automatiquement les problèmes multiplateformes. La seule raison d'utiliser os.path est de maintenir du code legacy qui doit supporter Python 2, ou d'utiliser les rares fonctions os qui n'ont pas d'équivalent pathlib (comme os.environ pour les variables d'environnement).

Est-ce que pathlib fonctionne sur Windows ?

Oui. pathlib utilise automatiquement des objets WindowsPath sur Windows et PosixPath sur Linux/macOS. L'opérateur / produit des chemins séparés par des antislashs sur Windows. Vous écrivez le même code sur toutes les plateformes et pathlib gère les différences.

Puis-je utiliser des objets Path avec Pandas ?

Oui. Depuis Python 3.6 et Pandas 0.21+, vous pouvez passer des objets Path directement à pd.read_csv(), pd.read_excel(), df.to_csv(), et autres fonctions d'E/S. Pas besoin de conversion avec str().

Quelle est la différence entre Path.resolve() et Path.absolute() ?

.resolve() retourne le chemin absolu et résout aussi les liens symboliques et les composants ../. .absolute() retourne le chemin absolu sans résoudre les liens symboliques ou normaliser le chemin. Dans la plupart des cas, c'est .resolve() que vous voulez.

Comment convertir entre objets Path et chaînes ?

Utilisez str(path) pour convertir un Path en chaîne. Utilisez Path(string) pour créer un Path à partir d'une chaîne. Vous pouvez aussi utiliser os.fspath(path) pour une conversion explicite en chaîne. La plupart des bibliothèques Python modernes acceptent les objets Path directement, donc la conversion est rarement nécessaire.

Conclusion

Le module pathlib de Python est la norme moderne pour la manipulation des chemins de fichiers. L'opérateur / rend le joint de chemins lisible. Les propriétés comme .name, .stem, .suffix, et .parent éliminent les chaînes verbeuses de fonctions os.path. Les méthodes intégrées pour lire, écrire, créer des répertoires et faire du globbing consolident ce qui nécessitait auparavant os, os.path, glob, et open() en une seule API cohérente.

La migration de os.path vers pathlib est simple : remplacez os.path.join() par /, remplacez os.path.exists() par .exists(), remplacez os.makedirs() par .mkdir(parents=True), et remplacez glob.glob() par .glob() ou .rglob(). Chaque bibliothèque Python majeure — Pandas, NumPy, PIL, PyTorch — accepte maintenant les objets Path nativement. Il n'y a aucune raison de l'éviter dans les nouveaux projets.

Commencez petit. Choisissez un script qui a du code os.path confus. Remplacez les opérations de chemin par pathlib. Le code deviendra plus court, plus lisible, et plus portable. Faites ensuite de même pour le script suivant.

📚