Apprendre Python de zéro jusqu'au niveau expert : syntaxe, structures de données, POO, générateurs, décorateurs, typage, tests, async et internals du langage.
Un script d'une page devient vite un projet de vingt fichiers. Ce chapitre couvre trois compétences qui marquent le passage du débutant à l'intermédiaire : découper son code en modules, lire et écrire des fichiers proprement, et gérer les erreurs au lieu de les subir.
Tout fichier .py est un module. Un dossier contenant des modules est un package.
app/
├── pyproject.toml
└── src/
└── boutique/
├── __init__.py
├── panier.py
└── paiement/
├── __init__.py
└── stripe.pyimport math
from pathlib import Path
from boutique.panier import Panier
from boutique.paiement import stripe as pay
import numpy as np # alias conventionnelÉvitez from module import * : on ne sait plus d'où vient un nom.
__main__Un module est exécuté à son import. Pour qu'un fichier soit à la fois importable et exécutable :
def slugifier(texte: str) -> str:
return "-".join(texte.lower().split())
if __name__ == "__main__":
print(slugifier("Mon Super Titre"))__name__ vaut "__main__" quand le fichier est lancé directement, et "outils" quand il est importé.
Python est livré « piles incluses ». Avant d'installer un paquet, vérifiez ce qui existe déjà :
| Module | Usage |
|---|---|
pathlib | chemins de fichiers |
json, csv, tomllib | formats de données |
datetime, zoneinfo | dates et fuseaux |
re | expressions régulières |
subprocess | lancer des commandes |
argparse | parser les arguments CLI |
logging | journalisation |
itertools, functools | outils fonctionnels |
sqlite3 | base SQL embarquée |
pathlib remplace os.path avec une API objet.
from pathlib import Path
racine = Path("data")
racine.mkdir(exist_ok=True)
fichier = racine / "notes.txt" # l'opérateur / joint les chemins
fichier.write_text("Bonjour\n", encoding="utf-8")
contenu = fichier.read_text(encoding="utf-8")
fichier.exists() # True
fichier.suffix # '.txt'
fichier.stem # 'notes'
for csv in racine.glob("**/*.csv"):
print(csv)Précisez toujours encoding="utf-8". Sur Windows, l'encodage par défaut a longtemps été cp1252, source de caractères corrompus.
Pour les gros fichiers, on lit ligne par ligne avec open() dans un bloc with. Le fichier est fermé automatiquement, même en cas d'erreur.
with open("access.log", encoding="utf-8") as f:
for ligne in f: # lecture paresseuse, pas tout en mémoire
if "500" in ligne:
print(ligne.rstrip())Modes : "r" lecture, "w" écriture (écrase), "a" ajout, "b" binaire ("rb", "wb").
import json, csv
data = {"nom": "Ada", "langages": ["Python", "C"]}
Path("user.json").write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
charge = json.loads(Path("user.json").read_text(encoding="utf-8"))
with open("ventes.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["produit"], float(row["montant"]))Une exception interrompt le flux normal. Si personne ne l'attrape, le programme s'arrête avec une traceback.
try:
age = int(input("Âge : "))
except ValueError:
print("Ce n'est pas un nombre")
else:
print(f"Dans 10 ans : {age + 10}") # si aucune exception
finally:
print("Toujours exécuté")Plusieurs types, et accès à l'objet exception :
try:
resultat = charger_config(chemin)
except FileNotFoundError:
resultat = CONFIG_PAR_DEFAUT
except (json.JSONDecodeError, KeyError) as e:
raise SystemExit(f"Config invalide : {e}")try:
traiter()
except Exception:
pass # le pire code Python qui existeCe bloc cache tous les bugs, y compris les fautes de frappe. Attrapez l'exception précise que vous savez gérer, laissez remonter le reste.
class StockInsuffisant(Exception):
def __init__(self, produit: str, demande: int, dispo: int):
super().__init__(f"{produit} : {demande} demandés, {dispo} en stock")
self.produit = produit
def retirer(produit, quantite):
dispo = stock[produit]
if quantite > dispo:
raise StockInsuffisant(produit, quantite, dispo)
stock[produit] -= quantiteChaîner une exception pour garder la cause d'origine :
try:
row = db.fetch(user_id)
except ConnectionError as e:
raise ServiceIndisponible("Base injoignable") from ePython préfère « Easier to Ask Forgiveness than Permission » : tenter l'opération et gérer l'échec, plutôt que tout vérifier avant.
# LBYL — race condition possible entre le test et l'ouverture
if chemin.exists():
data = chemin.read_text()
# EAFP — idiomatique
try:
data = chemin.read_text()
except FileNotFoundError:
data = ""import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
log = logging.getLogger(__name__)
log.info("Import démarré")
log.warning("Ligne %d ignorée", 42)
try:
1 / 0
except ZeroDivisionError:
log.exception("Calcul impossible") # inclut la tracebackimport argparse
from pathlib import Path
parser = argparse.ArgumentParser(description="Compte les lignes des fichiers")
parser.add_argument("dossier", type=Path)
parser.add_argument("--ext", default=".py")
args = parser.parse_args()
total = 0
for f in args.dossier.rglob(f"*{args.ext}"):
try:
n = sum(1 for _ in f.open(encoding="utf-8"))
except UnicodeDecodeError:
continue
total += n
print(f"{n:6} {f}")
print(f"{total:6} TOTAL")python compter.py src --ext .pyModules pour organiser, pathlib et with pour les fichiers, exceptions précises pour les erreurs : c'est l'hygiène de base d'un code Python qu'on peut maintenir. La suite passe à la programmation orientée objet.