45  Les modules sys, os et pathlib

Au niveau Expert, un programme Python interagit avec son environnement : lire des arguments de ligne de commande, explorer un dossier, gérer des chemins de fichiers de façon portable entre Windows, macOS et Linux. Trois modules standard couvrent ces besoins : sys (l’interpréteur), os (le système d’exploitation), et pathlib (les chemins modernes).

45.1 Le module sys

sys donne accès à des informations sur l’interpréteur Python lui-même et ses entrées/sorties.

Arguments de ligne de commande : sys.argv

Quand vous lancez python script.py arg1 arg2, sys.argv contient la liste ["script.py", "arg1", "arg2"].

# fichier : compter.py
import sys

if len(sys.argv) < 2:
    print("Usage : python compter.py <nom>")
    sys.exit(1)

nom = sys.argv[1]
print(f"Bonjour {nom} !")

Exécution :

$ python compter.py Alice
Bonjour Alice !
sys.argv[0] contient le nom du script

C’est pourquoi les vrais arguments commencent à l’indice 1. Convention universelle en informatique.

Arrêter le programme : sys.exit()

import sys

if erreur_grave:
    print("Erreur : impossible de continuer")
    sys.exit(1)           # code de sortie non-zero = erreur

Codes de sortie conventionnels :

  • 0 (défaut) : succès.
  • 1 : erreur générique.
  • 2 : mauvais usage / mauvais arguments.

Ces codes sont utilisés par les scripts shell (if python script.py; then ...).

La version de Python : sys.version

import sys

print(sys.version)
print(sys.version_info)
print(f"Python {sys.version_info.major}.{sys.version_info.minor}")
3.12.7 (tags/v3.12.7:0b05ead, Oct  1 2024, 03:06:41) [MSC v.1941 64 bit (AMD64)]
sys.version_info(major=3, minor=12, micro=7, releaselevel='final', serial=0)
Python 3.12

Utile pour des tests de compatibilité :

if sys.version_info < (3, 10):
    raise RuntimeError("Ce script requiert Python 3.10 minimum")

Les flux d’entrée/sortie

sys.stdin, sys.stdout, sys.stderr sont les flux standard.

import sys

# Écrire sur la sortie d'erreur (pratique pour les messages de debug)
print("Attention !", file=sys.stderr)

# Lire depuis stdin (utile pour les scripts qui acceptent des données piped)
for ligne in sys.stdin:
    print(f"Reçu : {ligne.strip()}")

Exemple de pipe Unix :

echo "hello\nworld" | python mon_script.py

Le chemin de recherche des modules : sys.path

Python cherche les modules dans les dossiers listés dans sys.path.

import sys
print(sys.path[:3], "...")       # 3 premiers éléments
['C:\\Users\\loich\\AppData\\Local\\Programs\\Python\\Python312\\python312.zip', 'C:\\Users\\loich\\AppData\\Local\\Programs\\Python\\Python312\\DLLs', 'C:\\Users\\loich\\AppData\\Local\\Programs\\Python\\Python312\\Lib'] ...

On peut le modifier à l’exécution si besoin (rare et déconseillé) :

import sys
sys.path.insert(0, "/chemin/vers/mon/dossier")
import mon_module     # maintenant trouvable

Préférez installer vos modules via pip dans un environnement virtuel.

45.2 Le module os

os offre des fonctions système portables (fonctionnent sur Windows, Linux, macOS).

Variables d’environnement : os.environ

import os

# Lire une variable d'environnement
home = os.environ.get("HOME", "défaut")
user = os.environ.get("USER", "anonyme")

print(f"HOME : {home}")
print(f"USER : {user}")

# Définir une variable pour les sous-processus
os.environ["MA_VARIABLE"] = "valeur"
HOME : défaut
USER : anonyme

Usage typique : récupérer des secrets (API keys, mots de passe) sans les mettre dans le code.

import os

API_KEY = os.environ.get("API_KEY")
if not API_KEY:
    raise RuntimeError("La variable d'environnement API_KEY n'est pas définie")

Répertoire courant : os.getcwd(), os.chdir()

import os

print("Répertoire actuel :", os.getcwd())

# Changer de répertoire (localement pour le programme)
# os.chdir("/tmp")
Répertoire actuel : C:\Users\loich\Documents\Work\Github\quarto\tosa-quarto\04-expert

Lister un dossier : os.listdir()

import os

# Contenu du répertoire courant
print(os.listdir("."))
['01-regex.html', '01-regex.qmd', '01-regex.quarto_ipynb', '01-regex_files', '02-iterateurs-generateurs.html', '02-iterateurs-generateurs.qmd', '02-iterateurs-generateurs.quarto_ipynb', '02-iterateurs-generateurs_files', '03-decorateurs.html', '03-decorateurs.qmd', '03-decorateurs.quarto_ipynb', '03-decorateurs_files', '04-scopes-closures.html', '04-scopes-closures.qmd', '04-scopes-closures.quarto_ipynb', '04-scopes-closures_files', '05-sys-os-pathlib.qmd', '05-sys-os-pathlib.quarto_ipynb', '06-qualite-code.qmd', '06-qualite-code.quarto_ipynb', '07-tests-unitaires.qmd', '07-tests-unitaires.quarto_ipynb', '08-type-hints.qmd', '08-type-hints.quarto_ipynb', '99-tp-parseur-logs.qmd', '99-tp-parseur-logs.quarto_ipynb', 'index.html', 'index.qmd']

Créer / supprimer : os.makedirs(), os.remove()

import os

# Créer un dossier (avec sous-dossiers intermédiaires)
os.makedirs("mon/dossier/imbrique", exist_ok=True)

# Supprimer un fichier
os.remove("fichier.txt")

# Supprimer un dossier vide
os.rmdir("dossier_vide")

# Supprimer un dossier et tout son contenu
import shutil
shutil.rmtree("dossier_non_vide")
⚠️ shutil.rmtree supprime récursivement sans confirmation

À utiliser avec prudence : un mauvais chemin et vous effacez beaucoup. Testez d’abord sur un dossier factice.

Chemins avec os.path (ancien style)

Le module os.path manipule les chemins sous forme de chaînes. Historiquement la référence, aujourd’hui dépassé par pathlib.

import os.path

chemin = "/home/user/documents/rapport.txt"

print(os.path.basename(chemin))       # 'rapport.txt'
print(os.path.dirname(chemin))        # '/home/user/documents'
print(os.path.splitext(chemin))       # ('/home/user/documents/rapport', '.txt')
print(os.path.exists(chemin))         # False (ce chemin n'existe pas)

# Assembler un chemin (portable)
chemin = os.path.join("dossier", "sous_dossier", "fichier.txt")
print(chemin)
rapport.txt
/home/user/documents
('/home/user/documents/rapport', '.txt')
False
dossier\sous_dossier\fichier.txt
Privilégiez pathlib pour le code moderne

os.path fonctionne, mais pathlib (vu plus bas) est plus lisible et objet. Connaître os.path reste utile pour lire du vieux code.

45.3 Le module pathlib — la voie moderne

pathlib (Python 3.4+) fournit une API orientée objet pour les chemins.

Créer un Path

from pathlib import Path

# Divers constructeurs
p1 = Path("/home/user/doc.txt")
p2 = Path("dossier", "sous", "fichier.txt")
p3 = Path.home()                 # dossier home de l'utilisateur
p4 = Path.cwd()                  # répertoire courant

print(p1)
print(p2)
print(p3)
print(p4)
\home\user\doc.txt
dossier\sous\fichier.txt
C:\Users\loich
C:\Users\loich\Documents\Work\Github\quarto\tosa-quarto\04-expert

Opérateur / pour assembler

from pathlib import Path

base = Path("/home/user")

rapport = base / "documents" / "rapport.txt"
print(rapport)
\home\user\documents\rapport.txt

Élégant et portable — fonctionne sur Windows (qui utilise \) sans changement de code.

Informations sur un chemin

from pathlib import Path

p = Path("/home/user/documents/rapport.pdf")

print(f"name         : {p.name}")        # 'rapport.pdf'
print(f"stem         : {p.stem}")         # 'rapport'
print(f"suffix       : {p.suffix}")       # '.pdf'
print(f"parent       : {p.parent}")       # '/home/user/documents'
print(f"parent.parent: {p.parent.parent}")
print(f"parts        : {p.parts}")        # tuple des composants
name         : rapport.pdf
stem         : rapport
suffix       : .pdf
parent       : \home\user\documents
parent.parent: \home\user
parts        : ('\\', 'home', 'user', 'documents', 'rapport.pdf')

Tests d’existence

from pathlib import Path

p = Path(".")

print(f"existe ?      : {p.exists()}")
print(f"est fichier ? : {p.is_file()}")
print(f"est dossier ? : {p.is_dir()}")
existe ?      : True
est fichier ? : False
est dossier ? : True

Itérer dans un dossier

from pathlib import Path

dossier = Path(".")

# Tous les éléments (fichiers ET dossiers) directs
for p in dossier.iterdir():
    type_ = "📁" if p.is_dir() else "📄"
    print(f"{type_} {p.name}")
📄 01-regex.html
📄 01-regex.qmd
📄 01-regex.quarto_ipynb
📁 01-regex_files
📄 02-iterateurs-generateurs.html
📄 02-iterateurs-generateurs.qmd
📄 02-iterateurs-generateurs.quarto_ipynb
📁 02-iterateurs-generateurs_files
📄 03-decorateurs.html
📄 03-decorateurs.qmd
📄 03-decorateurs.quarto_ipynb
📁 03-decorateurs_files
📄 04-scopes-closures.html
📄 04-scopes-closures.qmd
📄 04-scopes-closures.quarto_ipynb
📁 04-scopes-closures_files
📄 05-sys-os-pathlib.qmd
📄 05-sys-os-pathlib.quarto_ipynb
📄 06-qualite-code.qmd
📄 06-qualite-code.quarto_ipynb
📄 07-tests-unitaires.qmd
📄 07-tests-unitaires.quarto_ipynb
📄 08-type-hints.qmd
📄 08-type-hints.quarto_ipynb
📄 99-tp-parseur-logs.qmd
📄 99-tp-parseur-logs.quarto_ipynb
📄 index.html
📄 index.qmd

Recherche avec motifs : glob et rglob

from pathlib import Path

dossier = Path(".")

# Tous les .py dans le dossier courant (non récursif)
for p in dossier.glob("*.py"):
    print(p)

# Récursif sur toute l'arborescence
for p in dossier.rglob("*.py"):
    print(p)

# Patterns avancés
for p in dossier.glob("**/test_*.py"):     # tous les fichiers test_*.py à n'importe quelle profondeur
    print(p)

Syntaxe glob :

  • * : n’importe quels caractères (sauf /).
  • ? : un caractère.
  • ** : récursivité (**/*.py = .py à toute profondeur).
  • [abc] : classe de caractères.

Lecture / écriture rapide

pathlib offre des raccourcis pour les petits fichiers.

from pathlib import Path

p = Path("demo.txt")

# Écrire tout d'un coup
p.write_text("Hello World\nLine 2", encoding="utf-8")

# Lire tout
print(p.read_text(encoding="utf-8"))

# Nettoyer
p.unlink()
Hello World
Line 2

Pour les fichiers binaires : p.write_bytes(...) et p.read_bytes().

Quand utiliser pathlib vs open() ?
  • Petits fichiers (< quelques Mo) : p.read_text() / p.write_text() — concis.
  • Gros fichiers ou traitement ligne par ligne : with open(...) as f: — plus efficace (streaming).

Suppression et renommage

from pathlib import Path

p = Path("fichier.txt")

# Supprimer un fichier
p.unlink()                       # lève si absent
p.unlink(missing_ok=True)        # ne lève pas si absent (Python 3.8+)

# Renommer / déplacer
nouveau = p.rename("fichier_renomme.txt")

# Supprimer un dossier vide
Path("vide/").rmdir()

Créer un dossier

from pathlib import Path

# Créer un dossier simple (lève si existe déjà)
Path("nouveau").mkdir()

# Créer avec sous-dossiers intermédiaires, sans erreur si existe
Path("mon/dossier/imbrique").mkdir(parents=True, exist_ok=True)

Chemins absolus et résolus

from pathlib import Path

p = Path("rapport.txt")      # relatif
abs_p = p.resolve()          # absolu, avec résolution des liens symboliques

print(f"Relatif  : {p}")
print(f"Absolu   : {abs_p}")
Relatif  : rapport.txt
Absolu   : C:\Users\loich\Documents\Work\Github\quarto\tosa-quarto\04-expert\rapport.txt

45.4 Comparaison os/os.path vs pathlib

Pour les mêmes tâches :

Tâche os / os.path pathlib
Concaténer os.path.join("a", "b") Path("a") / "b"
Nom du fichier os.path.basename(p) p.name
Extension os.path.splitext(p)[1] p.suffix
Dossier parent os.path.dirname(p) p.parent
Existe ? os.path.exists(p) p.exists()
Lister os.listdir(d) Path(d).iterdir()
Lire un fichier open(p).read() Path(p).read_text()
Supprimer os.remove(p) p.unlink()

Verdict : pathlib est plus lisible, plus sûr, plus moderne. Utilisez-le dans tout nouveau code.

45.5 Exécution de commandes système

Pour lancer un programme externe depuis Python, le module standard est subprocess.

import subprocess
import sys

# sys.executable pointe vers l'interpréteur Python courant — portable
# (sur Windows, `echo` et `ls` sont des commandes internes au shell, pas des binaires)
resultat = subprocess.run(
    [sys.executable, "-c", "print('Hello from Python subprocess')"],
    capture_output=True,
    text=True,
)

print(f"Code retour : {resultat.returncode}")
print(f"Sortie     : {resultat.stdout.strip()}")
Code retour : 0
Sortie     : Hello from Python subprocess
Portabilité : echo, ls, dir

echo, ls, dir ne sont pas des exécutables : ce sont des commandes internes du shell (bash, cmd.exe, PowerShell). Pour les lancer via subprocess, deux options :

  • Ciblée : appeler le shell explicitement, ex. ["cmd", "/c", "dir"] (Windows) ou ["bash", "-c", "ls -la"] (Linux/Mac).
  • Portable : préférer un vrai binaire (python, git, curl…) ou shell=True — mais gare à l’injection si shell=True reçoit une entrée utilisateur.

Arguments clés :

  • capture_output=True : récupère stdout et stderr.
  • text=True : décode en str plutôt que bytes.
  • check=True : lève une exception si le code de retour ≠ 0.
  • shell=True : passe la commande au shell (dangereux si shell=True + entrée utilisateur).
⚠️ shell=True + entrée utilisateur = faille de sécurité

Ne faites jamais :

# ❌ Dangereux
subprocess.run(f"grep {user_input} fichier.txt", shell=True)

Un utilisateur malveillant peut injecter "; rm -rf /" ou similaire.

Bonne pratique : passez une liste (pas une chaîne) et évitez shell=True :

# ✅ Sûr
subprocess.run(["grep", user_input, "fichier.txt"])

45.6 Détecter le système d’exploitation

import sys
import platform

print(f"Platform    : {sys.platform}")   # 'linux', 'darwin' (Mac), 'win32'
print(f"System      : {platform.system()}")   # 'Linux', 'Darwin', 'Windows'
print(f"Release     : {platform.release()}")
print(f"Machine     : {platform.machine()}")
Platform    : win32
System      : Windows
Release     : 11
Machine     : AMD64

Cas d’usage : adapter le comportement selon l’OS.

import sys

if sys.platform.startswith("win"):
    clear_cmd = "cls"
else:
    clear_cmd = "clear"

print(f"Commande à utiliser : {clear_cmd}")
Commande à utiliser : cls

🧩 Quiz 5.1 — sys, os, pathlib

Question 1

Que contient sys.argv[0] ?

  1. Le premier argument passé au script
  2. Le nom du script lancé
  3. Le nom de l’interpréteur Python
  4. Une chaîne vide

b) — par convention, sys.argv[0] est le nom du script. Les arguments utilisateur commencent à sys.argv[1].

Question 2

Quelle est la différence entre sys.stdout et sys.stderr ?

  1. stdout est plus rapide
  2. stdout est la sortie normale, stderr est la sortie d’erreur
  3. stderr est désactivé par défaut
  4. Aucune

b) — ils sont redirigeables indépendamment dans un shell. Un script bien écrit met les messages d’erreur dans stderr et les résultats dans stdout.

Question 3

Comment accéder à la variable d’environnement HOME ?

  1. os.environ["HOME"]
  2. os.get_env("HOME")
  3. sys.argv["HOME"]
  4. env.HOME

a) os.environ["HOME"] — ou mieux, os.environ.get("HOME", "défaut") pour éviter KeyError si elle n’est pas définie.

Question 4

Que fait Path("a") / "b" / "c.txt" ?

  1. Une division mathématique
  2. Un chemin a/b/c.txt (ou a\b\c.txt sur Windows)
  3. Une erreur
  4. Une chaîne de caractères

b) — l’opérateur / est surchargé par pathlib pour concaténer les chemins de façon portable.

from pathlib import Path
print(Path("a") / "b" / "c.txt")
a\b\c.txt

Question 5

Sur p = Path("/home/user/rapport.pdf"), que vaut p.stem ?

  1. /home/user
  2. rapport.pdf
  3. rapport
  4. .pdf

c) rapport — le stem est le nom sans extension. p.suffix donne .pdf, p.name donne rapport.pdf.

from pathlib import Path
p = Path("/home/user/rapport.pdf")
print(p.stem, "|", p.suffix, "|", p.name)
rapport | .pdf | rapport.pdf

Question 6

Quelle méthode pathlib lit tout un fichier texte en une ligne ?

  1. p.read()
  2. p.read_file()
  3. p.read_text()
  4. p.open().read()

c) p.read_text() — raccourci pour les petits fichiers. Pour les gros, préférer with p.open() as f: + streaming.

Question 7

Que cherche list(Path(".").rglob("*.py")) ?

  1. Les fichiers .py dans le dossier courant uniquement
  2. Les fichiers .py dans tout l’arborescence, récursivement
  3. Les fichiers dont le nom contient py
  4. Rien, c’est une erreur

b)rglob est la version récursive de glob. Explore tous les sous-dossiers.

Question 8

Pour exécuter une commande système en toute sécurité, utilisez :

  1. os.system("cmd")
  2. subprocess.run(["cmd", "arg1"], shell=False)
  3. subprocess.run(f"cmd {arg}", shell=True) avec des inputs utilisateur
  4. eval("cmd")

b) — passer une liste et shell=False (défaut) évite les injections de commande. os.system est obsolète et moins sûr.


✏️ Exercice 5.1 — Analyseur d’arguments

Écrivez un script qui compte le nombre de mots dans le fichier passé en argument. Si le fichier n’existe pas, affichez une erreur et sortez avec code 1.

# compter_mots.py
import sys
from pathlib import Path

# Votre code ici

Usage :

$ python compter_mots.py rapport.txt
Le fichier contient 1234 mots.
# compter_mots.py
import sys
from pathlib import Path


def main():
    if len(sys.argv) != 2:
        print("Usage : python compter_mots.py <fichier>", file=sys.stderr)
        sys.exit(2)

    chemin = Path(sys.argv[1])

    if not chemin.is_file():
        print(f"Fichier introuvable : {chemin}", file=sys.stderr)
        sys.exit(1)

    contenu = chemin.read_text(encoding="utf-8")
    mots = contenu.split()

    print(f"Le fichier contient {len(mots)} mots.")


if __name__ == "__main__":
    main()

Test en Quarto

import sys
from pathlib import Path

# Créer un fichier de test
Path("test.txt").write_text("Hello world, ceci est un test.\nDeuxième ligne.", encoding="utf-8")


def compter_mots(chemin_str):
    chemin = Path(chemin_str)
    if not chemin.is_file():
        return None
    return len(chemin.read_text(encoding="utf-8").split())


print(f"Nombre de mots : {compter_mots('test.txt')}")
print(f"Fichier absent : {compter_mots('inexistant.txt')}")

# Nettoyage
Path("test.txt").unlink()
Nombre de mots : 8
Fichier absent : None

✏️ Exercice 5.2 — Organiser des fichiers par extension

Écrivez une fonction organiser(dossier) qui déplace chaque fichier du dossier vers un sous-dossier nommé selon son extension : .jpgjpg/, .pdfpdf/, etc.

from pathlib import Path
import shutil

def organiser(dossier):
    ...

# Test dans un dossier factice
from pathlib import Path
import shutil


def organiser(dossier):
    dossier = Path(dossier)
    if not dossier.is_dir():
        raise NotADirectoryError(f"{dossier} n'est pas un dossier")

    for fichier in dossier.iterdir():
        if not fichier.is_file():
            continue

        ext = fichier.suffix.lstrip(".").lower() or "sans_extension"
        cible = dossier / ext
        cible.mkdir(exist_ok=True)

        destination = cible / fichier.name
        shutil.move(str(fichier), str(destination))
        print(f"  {fichier.name}{ext}/")


# Test : créer un dossier factice avec quelques fichiers
test_dir = Path("test_organiser")
test_dir.mkdir(exist_ok=True)

for nom in ["photo1.jpg", "photo2.JPG", "doc.pdf", "notes.txt", "readme"]:
    (test_dir / nom).write_text("contenu", encoding="utf-8")

print("Avant :")
for f in sorted(test_dir.iterdir()):
    print(f"  {f.name}")

print("\nOrganisation :")
organiser(test_dir)

print("\nAprès :")
for sous in sorted(test_dir.iterdir()):
    if sous.is_dir():
        print(f"  📁 {sous.name}/")
        for f in sous.iterdir():
            print(f"      {f.name}")

# Nettoyage
shutil.rmtree(test_dir)
Avant :
  doc.pdf
  notes.txt
  photo1.jpg
  photo2.JPG
  readme

Organisation :
  doc.pdf → pdf/
  notes.txt → txt/
  photo1.jpg → jpg/
  photo2.JPG → jpg/
  readme → sans_extension/

Après :
  📁 jpg/
      photo1.jpg
      photo2.JPG
  📁 pdf/
      doc.pdf
  📁 sans_extension/
      readme
  📁 txt/
      notes.txt

Points à retenir

  • fichier.suffix.lstrip(".") : extraire l’extension sans le point.
  • .lower() : unifier les casses (.JPG et .jpg → même dossier).
  • shutil.move : déplace le fichier (nécessite str pour compatibilité).
  • Toujours créer le dossier cible avant de déplacer.

✏️ Exercice 5.3 — Récapitulatif d’un dossier

Écrivez une fonction resume_dossier(dossier) qui affiche :

  • Le nombre total de fichiers.
  • La taille totale (en Ko).
  • Les 3 fichiers les plus gros.
  • La répartition par extension.
from pathlib import Path
from collections import Counter

def resume_dossier(dossier):
    ...

resume_dossier(".")
from pathlib import Path
from collections import Counter


def resume_dossier(dossier):
    dossier = Path(dossier)
    fichiers = [f for f in dossier.rglob("*") if f.is_file()]

    if not fichiers:
        print("Aucun fichier.")
        return

    # Taille totale
    taille_totale = sum(f.stat().st_size for f in fichiers)

    # Top 3 des plus gros
    plus_gros = sorted(fichiers, key=lambda f: f.stat().st_size, reverse=True)[:3]

    # Répartition par extension
    extensions = Counter(f.suffix.lower() or "(sans)" for f in fichiers)

    # Affichage
    print(f"📂 {dossier.resolve()}")
    print(f"   Fichiers : {len(fichiers)}")
    print(f"   Taille   : {taille_totale / 1024:.1f} Ko")

    print("\n   Top 3 plus gros :")
    for f in plus_gros:
        taille = f.stat().st_size
        print(f"     {f.name:<40} {taille:>8} octets")

    print("\n   Par extension :")
    for ext, n in extensions.most_common():
        print(f"     {ext:<15} {n}")


# Créer un petit scénario de test
test_dir = Path("test_resume")
test_dir.mkdir(exist_ok=True)

(test_dir / "a.py").write_text("print('hello')")
(test_dir / "b.py").write_text("# " * 100)
(test_dir / "doc.md").write_text("# Titre\n" * 50)
(test_dir / "data.csv").write_text("col1,col2\n" * 200)

resume_dossier(test_dir)

# Nettoyage
import shutil
shutil.rmtree(test_dir)
📂 C:\Users\loich\Documents\Work\Github\quarto\tosa-quarto\04-expert\test_resume
   Fichiers : 4
   Taille   : 2.8 Ko

   Top 3 plus gros :
     data.csv                                     2200 octets
     doc.md                                        450 octets
     b.py                                          200 octets

   Par extension :
     .py             2
     .csv            1
     .md             1

Points à retenir

  • rglob("*") explore récursivement et renvoie fichiers et dossiers. On filtre avec is_file().
  • f.stat().st_size donne la taille en octets.
  • Counter (vu chapitre regex) compte les occurrences, .most_common() trie par décroissant.
  • La boucle sur les fichiers utilise .stat() — une seule fois par fichier (un appel système).

✏️ Exercice 5.4 — Portabilité multi-OS

Écrivez une fonction chemin_config() qui renvoie le bon chemin du dossier de config selon l’OS :

  • Windows : %APPDATA%/MonApp
  • Linux/Mac : ~/.config/mon_app

Utilisez sys.platform pour détecter.

import sys
from pathlib import Path

def chemin_config():
    ...

print(chemin_config())
import sys
import os
from pathlib import Path


def chemin_config(nom_app="mon_app"):
    if sys.platform.startswith("win"):
        base = Path(os.environ.get("APPDATA", Path.home()))
        return base / nom_app
    elif sys.platform == "darwin":        # macOS
        return Path.home() / "Library" / "Application Support" / nom_app
    else:                                   # Linux / autres Unix
        xdg = os.environ.get("XDG_CONFIG_HOME")
        base = Path(xdg) if xdg else Path.home() / ".config"
        return base / nom_app


print(f"Plateforme : {sys.platform}")
print(f"Chemin de config : {chemin_config('MonApp')}")
Plateforme : win32
Chemin de config : C:\Users\loich\AppData\Roaming\MonApp

Points à retenir

  • sys.platform : 'win32', 'darwin' (Mac), 'linux'.
  • os.environ.get(clé, défaut) pour lire une variable d’env avec défaut.
  • Sur Linux, XDG Base Directory est la convention moderne (~/.config/).
  • L’utilisation de pathlib rend tout ça portable — pas de mélange \ et /.

À retenir

Points clés du chapitre
  1. sys.argv : arguments de ligne de commande. sys.argv[0] = nom du script.
  2. sys.exit(code) pour terminer. Code 0 = succès, non-zero = erreur.
  3. sys.stdout, sys.stderr : distinguer messages normaux et erreurs.
  4. os.environ : variables d’environnement — utile pour les secrets.
  5. pathlib.Path : API moderne pour les chemins. Préférer à os.path.
  6. Path("a") / "b" : opérateur / pour assembler (portable).
  7. Attributs utiles : .name, .stem, .suffix, .parent, .parts.
  8. glob("*.py") / rglob("*.py") : recherche avec motifs, récursive ou non.
  9. subprocess.run([...], shell=False) pour exécuter du shell en sécurité.
  10. sys.platform pour détecter l’OS. shutil pour les opérations haut niveau.

← Chapitre précédent : Scopes et closuresChapitre suivant : Qualité du code →