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"].
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):raiseRuntimeError("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 sysprint(sys.path[:3], "...") # 3 premiers éléments
import os# Créer un dossier (avec sous-dossiers intermédiaires)os.makedirs("mon/dossier/imbrique", exist_ok=True)# Supprimer un fichieros.remove("fichier.txt")# Supprimer un dossier videos.rmdir("dossier_vide")# Supprimer un dossier et tout son contenuimport shutilshutil.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.
existe ? : True
est fichier ? : False
est dossier ? : True
Itérer dans un dossier
from pathlib import Pathdossier = Path(".")# Tous les éléments (fichiers ET dossiers) directsfor p in dossier.iterdir(): type_ ="📁"if p.is_dir() else"📄"print(f"{type_}{p.name}")
from pathlib import Pathdossier = Path(".")# Tous les .py dans le dossier courant (non récursif)for p in dossier.glob("*.py"):print(p)# Récursif sur toute l'arborescencefor p in dossier.rglob("*.py"):print(p)# Patterns avancésfor p in dossier.glob("**/test_*.py"): # tous les fichiers test_*.py à n'importe quelle profondeurprint(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 Pathp = Path("demo.txt")# Écrire tout d'un coupp.write_text("Hello World\nLine 2", encoding="utf-8")# Lire toutprint(p.read_text(encoding="utf-8"))# Nettoyerp.unlink()
Hello World
Line 2
Pour les fichiers binaires : p.write_bytes(...) et p.read_bytes().
Gros fichiers ou traitement ligne par ligne : with open(...) as f: — plus efficace (streaming).
Suppression et renommage
from pathlib import Pathp = Path("fichier.txt")# Supprimer un fichierp.unlink() # lève si absentp.unlink(missing_ok=True) # ne lève pas si absent (Python 3.8+)# Renommer / déplacernouveau = p.rename("fichier_renomme.txt")# Supprimer un dossier videPath("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 existePath("mon/dossier/imbrique").mkdir(parents=True, exist_ok=True)
Chemins absolus et résolus
from pathlib import Pathp = Path("rapport.txt") # relatifabs_p = p.resolve() # absolu, avec résolution des liens symboliquesprint(f"Relatif : {p}")print(f"Absolu : {abs_p}")
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 subprocessimport 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é
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 ?
stdout est plus rapide
stdout est la sortie normale, stderr est la sortie d’erreur
stderr est désactivé par défaut
Aucune
🔍 Réponse
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 ?
os.environ["HOME"]
os.get_env("HOME")
sys.argv["HOME"]
env.HOME
🔍 Réponse
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" ?
Une division mathématique
Un chemin a/b/c.txt (ou a\b\c.txt sur Windows)
Une erreur
Une chaîne de caractères
🔍 Réponse
b) — l’opérateur / est surchargé par pathlib pour concaténer les chemins de façon portable.
from pathlib import Pathprint(Path("a") /"b"/"c.txt")
a\b\c.txt
Question 5
Sur p = Path("/home/user/rapport.pdf"), que vaut p.stem ?
/home/user
rapport.pdf
rapport
.pdf
🔍 Réponse
c) rapport — le stem est le nom sans extension. p.suffix donne .pdf, p.name donne rapport.pdf.
from pathlib import Pathp = 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 ?
p.read()
p.read_file()
p.read_text()
p.open().read()
🔍 Réponse
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")) ?
Les fichiers .py dans le dossier courant uniquement
Les fichiers .py dans tout l’arborescence, récursivement
Les fichiers dont le nom contient py
Rien, c’est une erreur
🔍 Réponse
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 :
os.system("cmd")
subprocess.run(["cmd", "arg1"], shell=False)
subprocess.run(f"cmd {arg}", shell=True) avec des inputs utilisateur
eval("cmd")
🔍 Réponse
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.
import sysfrom pathlib import Path# Créer un fichier de testPath("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)ifnot chemin.is_file():returnNonereturnlen(chemin.read_text(encoding="utf-8").split())print(f"Nombre de mots : {compter_mots('test.txt')}")print(f"Fichier absent : {compter_mots('inexistant.txt')}")# NettoyagePath("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 : .jpg → jpg/, .pdf → pdf/, etc.
from pathlib import Pathfrom collections import Counterdef resume_dossier(dossier): ...resume_dossier(".")
from pathlib import Pathfrom collections import Counterdef resume_dossier(dossier): dossier = Path(dossier) fichiers = [f for f in dossier.rglob("*") if f.is_file()]ifnot 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)# Affichageprint(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_sizeprint(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 testtest_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)# Nettoyageimport shutilshutil.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 :