Bonnes pratiques et idiomes pythoniques#
Le Zen de Python#
Avant d’aborder les règles pratiques, il convient de s’arrêter sur la philosophie qui sous-tend toute l’esthétique du langage. Le Zen de Python est un ensemble de dix-neuf aphorismes rédigés par Tim Peters et intégrés à Python comme un œuf de Pâques accessible via import this. Ce texte n’est pas qu’une curiosité : il constitue une véritable charte de conception que les Pythonistes expérimentés intériorisent et appliquent naturellement.
import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Quelques aphorismes méritent une attention particulière dans le contexte des bonnes pratiques. Readability counts (la lisibilité compte) : en Python, le code est lu beaucoup plus souvent qu’il n’est écrit — par des collègues, par vous-même six mois plus tard, par des outils d’analyse. Toute décision de style qui améliore la lisibilité est préférable. Explicit is better than implicit : Python décourage la magie invisible et les comportements implicites. Une variable, une fonction, un module doivent avoir un nom qui dit ce qu’ils font. Simple is better than complex, Complex is better than complicated : le but n’est pas la simplicité naïve, mais l’élégance — la solution la plus simple qui fasse correctement le travail. There should be one obvious way to do it : contrairement à des langages qui encouragent plusieurs styles pour la même chose, Python converge vers une façon idiomatique pour chaque problème courant. Connaître ces idiomes, c’est parler couramment le langage.
Nommage PEP 8#
La PEP 8 (Style Guide for Python Code) est le document de référence sur le style en Python. Elle couvre l’indentation, la longueur des lignes, les espaces, les imports et, de façon centrale, les conventions de nommage. Ces conventions ne sont pas des règles arbitraires : elles codifient les pratiques unanimement adoptées par l’écosystème, ce qui rend tout code Python reconnaissable et lisible par n’importe quel Pythoniste.
Définition 41 (Conventions de nommage PEP 8)
Les conventions de nommage Python sont les suivantes :
snake_case— variables, fonctions, méthodes, paramètres, modules :ma_variable,calculer_surface,nom_fichier.SCREAMING_SNAKE_CASE— constantes de module :MAX_CONNEXIONS,PI,CHEMIN_CONFIG.PascalCase(ou CapWords) — classes, exceptions :MaClasse,ErreurReseau,ConfigurationServeur._préfixe_simple— convention pour les membres « privés » (pas d’accès externe attendu) :_cache,_calculer_hash.__préfixe_double— déclenche le name mangling dans les classes (protection contre les sous-classes) :__valeur_interne.__dunder__— méthodes spéciales réservées au runtime Python :__init__,__len__,__iter__.
# Bon nommage PEP 8
TAUX_TVA = 0.20 # Constante de module
def calculer_prix_ttc(prix_ht: float, taux: float = TAUX_TVA) -> float:
"""Calcule le prix TTC à partir du prix HT et du taux de TVA."""
return prix_ht * (1 + taux)
class CompteBancaire:
"""Représente un compte bancaire avec solde et historique."""
def __init__(self, titulaire: str, solde_initial: float = 0.0) -> None:
self.titulaire = titulaire
self._solde = solde_initial # Convention privé
self._historique: list[float] = []
@property
def solde(self) -> float:
return self._solde
def déposer(self, montant: float) -> None:
if montant <= 0:
raise ValueError("Le montant doit être positif")
self._solde += montant
self._historique.append(montant)
def __repr__(self) -> str:
return f"CompteBancaire({self.titulaire!r}, solde={self._solde:.2f})"
compte = CompteBancaire("Alice", 1000.0)
compte.déposer(500.0)
print(compte)
print(f"Prix TTC : {calculer_prix_ttc(100):.2f} €")
CompteBancaire('Alice', solde=1500.00)
Prix TTC : 120.00 €
Compréhensions#
Les compréhensions sont l’une des caractéristiques les plus emblématiques du style pythonique. Elles permettent de construire des listes, des dictionnaires et des ensembles de façon concise et expressive, en combinant transformation et filtrage en une seule expression.
# ─── Compréhensions de listes ───
carrés = [x**2 for x in range(10)]
pairs = [x for x in range(20) if x % 2 == 0]
mots_longs = [mot.upper() for mot in ["python", "est", "élégant", "et", "expressif"]
if len(mot) > 3]
print(carrés)
print(pairs)
print(mots_longs)
# Compréhension imbriquée : produit cartésien
combinaisons = [(x, y) for x in range(3) for y in range(3) if x != y]
print(combinaisons)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
['PYTHON', 'ÉLÉGANT', 'EXPRESSIF']
[(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
# ─── Compréhensions de dictionnaires ───
noms = ["alice", "bob", "charlie"]
longueurs = {nom: len(nom) for nom in noms}
print(longueurs)
# Inversion d'un dictionnaire (quand les valeurs sont uniques)
original = {"a": 1, "b": 2, "c": 3}
inversé = {v: k for k, v in original.items()}
print(inversé)
# Filtrage d'un dictionnaire
notes = {"alice": 17, "bob": 12, "charlie": 15, "david": 9}
admis = {nom: note for nom, note in notes.items() if note >= 10}
print(admis)
{'alice': 5, 'bob': 3, 'charlie': 7}
{1: 'a', 2: 'b', 3: 'c'}
{'alice': 17, 'bob': 12, 'charlie': 15}
# ─── Compréhensions d'ensembles ───
données = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
valeurs_uniques = {x for x in données}
print(valeurs_uniques)
# Lettres uniques dans une phrase, en minuscules
phrase = "Le Zen de Python est une philosophie"
lettres = {c.lower() for c in phrase if c.isalpha()}
print(sorted(lettres))
{1, 2, 3, 4}
['d', 'e', 'h', 'i', 'l', 'n', 'o', 'p', 's', 't', 'u', 'y', 'z']
Remarque 37
La règle pratique pour les compréhensions : elles sont appropriées lorsqu’elles tiennent sur une ou deux lignes et restent lisibles. Si la logique est trop complexe — plus d’une condition, plusieurs niveaux d’imbrication, des appels de fonctions multiples — il vaut mieux utiliser une boucle for classique ou extraire la logique dans une fonction séparée. La lisibilité prime toujours sur la concision.
Unpacking#
L”unpacking (déstructuration) est la capacité à distribuer les éléments d’un itérable dans plusieurs variables en une seule instruction. C’est un idiome pythonique puissant qui élimine beaucoup d’indexations explicites et rend le code plus auto-documenté.
# Unpacking de tuples et de listes
premier, deuxième, troisième = [10, 20, 30]
print(premier, deuxième, troisième)
# Unpacking avec * (collecte le reste)
tête, *milieu, queue = [1, 2, 3, 4, 5]
print(f"tête={tête}, milieu={milieu}, queue={queue}")
premier, *reste = range(5)
print(f"premier={premier}, reste={reste}")
# Échange de variables — sans variable temporaire
a, b = 10, 20
a, b = b, a
print(f"a={a}, b={b}")
# Unpacking imbriqué
point = (3, (4, 5))
x, (y, z) = point
print(f"x={x}, y={y}, z={z}")
10 20 30
tête=1, milieu=[2, 3, 4], queue=5
premier=0, reste=[1, 2, 3, 4]
a=20, b=10
x=3, y=4, z=5
# Unpacking dans les boucles
coordonnées = [(1, 2), (3, 4), (5, 6)]
for x, y in coordonnées:
print(f" ({x}, {y})")
# enumerate() — idiome fondamental pour itérer avec un indice
fruits = ["pomme", "poire", "cerise"]
for indice, fruit in enumerate(fruits, start=1):
print(f" {indice}. {fruit}")
# zip() — itération simultanée sur plusieurs séquences
prénoms = ["Alice", "Bob", "Charlie"]
âges = [30, 25, 35]
for prénom, âge in zip(prénoms, âges):
print(f" {prénom} a {âge} ans")
(1, 2)
(3, 4)
(5, 6)
1. pomme
2. poire
3. cerise
Alice a 30 ans
Bob a 25 ans
Charlie a 35 ans
# Unpacking dans les appels de fonctions
def créer_connexion(hôte: str, port: int, timeout: float = 30.0) -> str:
return f"Connexion à {hôte}:{port} (timeout={timeout}s)"
paramètres = ("localhost", 5432)
options = {"timeout": 10.0}
print(créer_connexion(*paramètres, **options))
Connexion à localhost:5432 (timeout=10.0s)
f-strings avancées#
Les f-strings (formatted string literals), introduites en Python 3.6 et enrichies dans les versions ultérieures, sont le moyen idiomatique de formater des chaînes en Python. Elles sont plus lisibles, plus rapides, et offrent plus de fonctionnalités que .format() ou %.
import math
from datetime import date
# Expressions arbitraires dans les f-strings
n = 42
print(f"La factorielle de {n} est {math.factorial(n):,}")
print(f"La racine carrée de {n:.2f} est {math.sqrt(n):.4f}")
# Formatage de nombres
pi = math.pi
print(f"π = {pi:.6f}") # 6 décimales
print(f"π = {pi:10.4f}") # largeur 10, 4 décimales
print(f"π = {pi:e}") # notation scientifique
print(f"42 en binaire : {42:08b}") # binaire sur 8 bits avec zéros
print(f"42 en hexadécimal : {42:#x}") # hexadécimal avec préfixe
# Formatage de dates
aujourd_hui = date(2026, 3, 19)
print(f"Aujourd'hui : {aujourd_hui:%A %d %B %Y}") # format strftime dans f-string
La factorielle de 42 est 1,405,006,117,752,879,898,543,142,606,244,511,569,936,384,000,000,000
La racine carrée de 42.00 est 6.4807
π = 3.141593
π = 3.1416
π = 3.141593e+00
42 en binaire : 00101010
42 en hexadécimal : 0x2a
Aujourd'hui : Thursday 19 March 2026
# Débogage avec = (Python 3.8+)
x = 42
y = [1, 2, 3]
print(f"{x = }") # Affiche "x = 42"
print(f"{y = }") # Affiche "y = [1, 2, 3]"
print(f"{len(y) = }") # Affiche "len(y) = 3"
print(f"{x * 2 + 1 = }") # Affiche "x * 2 + 1 = 85"
# Alignement et remplissage
for valeur in [1, 10, 100, 1000]:
print(f" {valeur:>6,} | {'*' * min(valeur // 100, 20)}")
x = 42
y = [1, 2, 3]
len(y) = 3
x * 2 + 1 = 85
1 |
10 |
100 | *
1,000 | **********
# f-strings multilignes
nom = "Alice"
montant = 1234.56
articles = ["Python avancé", "NumPy", "Pandas"]
facture = f"""
╔══════════════════════════════╗
║ Facture pour : {nom:<12}║
║ Montant : {montant:>18,.2f} € ║
║ Articles : ║
""".rstrip()
for i, art in enumerate(articles, 1):
facture += f"\n║ {i}. {art:<24}║"
facture += "\n╚══════════════════════════════╝"
print(facture)
╔══════════════════════════════╗
║ Facture pour : Alice ║
║ Montant : 1,234.56 € ║
║ Articles : ║
║ 1. Python avancé ║
║ 2. NumPy ║
║ 3. Pandas ║
╚══════════════════════════════╝
L’opérateur walrus (:=)#
L”opérateur walrus (:=), introduit en Python 3.8, est l”opérateur d’assignation dans une expression (assignment expression). Il permet d’assigner une valeur à une variable tout en utilisant cette valeur dans une expression, ce qui évite les calculs redondants et simplifie certains patterns.
# Sans walrus : calcul redondant
données = [1, 5, 3, 8, 2, 9, 4]
résultat_1 = [y for x in données if (y := x**2) > 20]
print(f"Carrés supérieurs à 20 : {résultat_1}")
# Dans une boucle while — pattern classique
import io
flux = io.StringIO("ligne 1\nligne 2\nligne 3\n")
lignes = []
while ligne := flux.readline():
lignes.append(ligne.rstrip())
print(f"Lignes lues : {lignes}")
# Éviter un double appel de fonction
import re
texte = "Réunion le 2026-03-19 à 14h30"
if correspondance := re.search(r"\d{4}-\d{2}-\d{2}", texte):
print(f"Date trouvée : {correspondance.group()}")
Carrés supérieurs à 20 : [25, 64, 81]
Lignes lues : ['ligne 1', 'ligne 2', 'ligne 3']
Date trouvée : 2026-03-19
Remarque 38
L’opérateur walrus doit être utilisé avec parcimonie. Il est particulièrement adapté aux boucles while qui lisent des données, et aux cas où une expression coûteuse est à la fois testée et utilisée. En revanche, l’employer dans des compréhensions complexes ou dans des conditions imbriquées peut nuire à la lisibilité — exactement ce que le Zen de Python cherche à éviter.
match / case — filtrage par motif#
Introduit en Python 3.10, le filtrage par motif structurel (match/case) est bien plus qu’un switch/case traditionnel. Il permet de déstructurer des objets, des tuples, des dictionnaires et des classes directement dans les branches, en combinant tests de type, d’égalité et d’attributs.
def décrire_forme(forme) -> str:
match forme:
case {"type": "cercle", "rayon": r} if r > 0:
return f"Cercle de rayon {r} (surface : {3.14159 * r**2:.2f})"
case {"type": "rectangle", "largeur": l, "hauteur": h}:
return f"Rectangle {l}×{h} (surface : {l * h})"
case {"type": "triangle", "base": b, "hauteur": h}:
return f"Triangle base={b}, hauteur={h} (surface : {b * h / 2:.2f})"
case {"type": type_inconnu}:
return f"Forme inconnue : {type_inconnu!r}"
case _:
return "Objet non reconnu"
formes = [
{"type": "cercle", "rayon": 5},
{"type": "rectangle", "largeur": 4, "hauteur": 6},
{"type": "triangle", "base": 3, "hauteur": 8},
{"type": "pentagone"},
42,
]
for f in formes:
print(décrire_forme(f))
Cercle de rayon 5 (surface : 78.54)
Rectangle 4×6 (surface : 24)
Triangle base=3, hauteur=8 (surface : 12.00)
Forme inconnue : 'pentagone'
Objet non reconnu
# match sur des tuples et des valeurs littérales
def interpréter_commande(commande: tuple) -> str:
match commande:
case ("quitter",):
return "Au revoir !"
case ("aller", direction) if direction in ("nord", "sud", "est", "ouest"):
return f"Vous allez vers le {direction}."
case ("prendre", objet):
return f"Vous prenez {objet!r}."
case ("attaquer", cible, arme):
return f"Vous attaquez {cible!r} avec {arme!r}."
case _:
return f"Commande inconnue : {commande}"
commandes = [
("aller", "nord"),
("prendre", "épée"),
("attaquer", "dragon", "lance"),
("quitter",),
("voler", "vers", "le", "soleil"),
]
for cmd in commandes:
print(interpréter_commande(cmd))
Vous allez vers le nord.
Vous prenez 'épée'.
Vous attaquez 'dragon' avec 'lance'.
Au revoir !
Commande inconnue : ('voler', 'vers', 'le', 'soleil')
pathlib — manipulation de chemins#
Le module pathlib (Python 3.4+) propose une API orientée objet pour la manipulation des chemins de fichiers, bien supérieure à la combinaison os + os.path qu’elle remplace avantageusement.
from pathlib import Path
# Création et composition de chemins
chemin = Path("/tmp") / "projets" / "mon_projet"
print(f"Chemin : {chemin}")
print(f"Parent : {chemin.parent}")
print(f"Nom : {chemin.name}")
print(f"Suffixe : {chemin.suffix}")
print(f"Suffixes : {chemin.suffixes}")
print(f"Racine : {chemin.root}")
print(f"Existe : {chemin.exists()}")
# Chemin vers le fichier courant (dans un vrai module)
# dossier_courant = Path(__file__).parent
# ressource = dossier_courant / "data" / "config.json"
# Opérations courantes
répertoire_home = Path.home()
print(f"\nRépertoire personnel : {répertoire_home}")
print(f"Chemin absolu de / : {Path('/').absolute()}")
# Itération sur un répertoire
import tempfile, os
with tempfile.TemporaryDirectory() as tmpdir:
tmp = Path(tmpdir)
(tmp / "a.py").write_text("# module a")
(tmp / "b.py").write_text("# module b")
(tmp / "notes.txt").write_text("notes")
print(f"\nFichiers dans {tmp.name} :")
for fichier in sorted(tmp.iterdir()):
print(f" {fichier.name} ({fichier.stat().st_size} octets)")
print("\nFichiers .py uniquement :")
for py in sorted(tmp.glob("*.py")):
print(f" {py.name}")
Chemin : /tmp/projets/mon_projet
Parent : /tmp/projets
Nom : mon_projet
Suffixe :
Suffixes : []
Racine : /
Existe : False
Répertoire personnel : /home/loc
Chemin absolu de / : /
Fichiers dans tmp7u1ap_n5 :
a.py (10 octets)
b.py (10 octets)
notes.txt (5 octets)
Fichiers .py uniquement :
a.py
b.py
collections — structures de données spécialisées#
Le module collections offre des structures de données complémentaires aux types intégrés, souvent plus expressives et plus efficaces pour des besoins spécifiques.
from collections import Counter, defaultdict, namedtuple, deque, OrderedDict
# ─── Counter : compter les occurrences ───
texte = "the quick brown fox jumps over the lazy dog"
compte_lettres = Counter(texte.replace(" ", ""))
print("10 lettres les plus fréquentes :")
print(compte_lettres.most_common(10))
# Opérations arithmétiques sur les Counter
c1 = Counter(python=5, java=3, rust=2)
c2 = Counter(python=2, go=4, rust=1)
print(f"\nUnion : {c1 + c2}")
print(f"Soustraction : {c1 - c2}")
print(f"Intersection : {c1 & c2}")
10 lettres les plus fréquentes :
[('o', 4), ('e', 3), ('t', 2), ('h', 2), ('u', 2), ('r', 2), ('q', 1), ('i', 1), ('c', 1), ('k', 1)]
Union : Counter({'python': 7, 'go': 4, 'java': 3, 'rust': 3})
Soustraction : Counter({'python': 3, 'java': 3, 'rust': 1})
Intersection : Counter({'python': 2, 'rust': 1})
# ─── defaultdict : valeur par défaut automatique ───
# Sans defaultdict : pattern lourd
mots_par_longueur_lourd: dict = {}
mots = ["python", "est", "un", "langage", "élégant"]
for mot in mots:
longueur = len(mot)
if longueur not in mots_par_longueur_lourd:
mots_par_longueur_lourd[longueur] = []
mots_par_longueur_lourd[longueur].append(mot)
# Avec defaultdict : idiomatique et concis
mots_par_longueur = defaultdict(list)
for mot in mots:
mots_par_longueur[len(mot)].append(mot)
print("Mots groupés par longueur :")
for longueur, groupe in sorted(mots_par_longueur.items()):
print(f" {longueur} lettres : {groupe}")
Mots groupés par longueur :
2 lettres : ['un']
3 lettres : ['est']
6 lettres : ['python']
7 lettres : ['langage', 'élégant']
# ─── namedtuple : tuple avec champs nommés ───
# (Préférer dataclass pour les cas plus complexes)
Point = namedtuple("Point", ["x", "y"])
Couleur = namedtuple("Couleur", ["rouge", "vert", "bleu"])
p = Point(3.0, 4.5)
rouge = Couleur(255, 0, 0)
print(f"Point : {p}, x={p.x}, y={p.y}")
print(f"Distance à l'origine : {(p.x**2 + p.y**2)**0.5:.3f}")
print(f"Couleur rouge : {rouge}")
print(f"Composante rouge : {rouge.rouge}")
# Les namedtuples sont toujours des tuples (immuables, décompressables)
x, y = p
print(f"Décompressé : x={x}, y={y}")
Point : Point(x=3.0, y=4.5), x=3.0, y=4.5
Distance à l'origine : 5.408
Couleur rouge : Couleur(rouge=255, vert=0, bleu=0)
Composante rouge : 255
Décompressé : x=3.0, y=4.5
# ─── deque : file double-terminée ───
from collections import deque
historique = deque(maxlen=5) # Garde seulement les 5 derniers éléments
for i in range(8):
historique.append(f"page_{i}")
print(f"Après append page_{i} : {list(historique)}")
Après append page_0 : ['page_0']
Après append page_1 : ['page_0', 'page_1']
Après append page_2 : ['page_0', 'page_1', 'page_2']
Après append page_3 : ['page_0', 'page_1', 'page_2', 'page_3']
Après append page_4 : ['page_0', 'page_1', 'page_2', 'page_3', 'page_4']
Après append page_5 : ['page_1', 'page_2', 'page_3', 'page_4', 'page_5']
Après append page_6 : ['page_2', 'page_3', 'page_4', 'page_5', 'page_6']
Après append page_7 : ['page_3', 'page_4', 'page_5', 'page_6', 'page_7']
contextlib — outils pour les gestionnaires de contexte#
Le module contextlib simplifie la création de gestionnaires de contexte grâce à contextmanager, qui transforme un générateur en gestionnaire de contexte utilisable avec with.
from contextlib import contextmanager, suppress, redirect_stdout
import io
# ─── contextmanager : créer un gestionnaire depuis un générateur ───
@contextmanager
def chronomètre(description: str = "opération"):
"""Mesure la durée d'un bloc de code."""
import time
début = time.perf_counter()
try:
yield
finally:
durée = time.perf_counter() - début
print(f"'{description}' : {durée * 1000:.3f} ms")
with chronomètre("calcul de somme"):
total = sum(range(1_000_000))
print(f"Total : {total:,}")
# ─── suppress : ignorer des exceptions spécifiques ───
with suppress(FileNotFoundError):
import os
os.remove("/tmp/fichier_inexistant_abc.txt")
print("Aucune erreur levée même si le fichier n'existait pas")
# ─── redirect_stdout : capturer la sortie standard ───
sortie = io.StringIO()
with redirect_stdout(sortie):
print("Ce message est capturé")
print("Celui-ci aussi")
contenu = sortie.getvalue()
print(f"Sortie capturée : {contenu!r}")
'calcul de somme' : 20.726 ms
Total : 499,999,500,000
Aucune erreur levée même si le fichier n'existait pas
Sortie capturée : 'Ce message est capturé\nCelui-ci aussi\n'
# ─── contextmanager pour gérer des ressources ───
@contextmanager
def transaction(connexion_simulée: list):
"""Gestionnaire de transaction avec commit/rollback automatique."""
sauvegarde = connexion_simulée.copy()
try:
yield connexion_simulée
print("COMMIT : transaction validée")
except Exception as e:
connexion_simulée.clear()
connexion_simulée.extend(sauvegarde)
print(f"ROLLBACK : transaction annulée ({e})")
raise
données = [1, 2, 3]
with transaction(données) as db:
db.append(4)
db.append(5)
print(f"Après commit : {données}")
try:
with transaction(données) as db:
db.append(99)
raise ValueError("Erreur simulée")
except ValueError:
pass
print(f"Après rollback : {données}")
COMMIT : transaction validée
Après commit : [1, 2, 3, 4, 5]
ROLLBACK : transaction annulée (Erreur simulée)
Après rollback : [1, 2, 3, 4, 5]
Anti-patterns courants#
Connaître les idiomes positifs ne suffit pas : il faut aussi connaître les pièges à éviter.
Exemple 10 (Anti-patterns fréquents et leurs corrections)
Voici les erreurs les plus fréquemment rencontrées dans du code Python non idiomatique.
# ✗ Comparer avec True/False de façon explicite
if ma_liste == True: ... # Jamais
if len(ma_liste) > 0: ... # Inutilement verbeux
# ✓ Pythonique : tirer parti de la vérité implicite
if ma_liste: ...
# ✗ Construire une liste pour itérer immédiatement dessus
for x in list(range(1_000_000)): # list() inutile
...
# ✓ range est déjà un itérable
for x in range(1_000_000):
...
# ✗ Concaténation de chaînes en boucle (O(n²) en mémoire)
résultat = ""
for mot in mots:
résultat += mot + " "
# ✓ join() est la bonne approche
résultat = " ".join(mots)
# ✗ Attraper Exception de façon trop large
try:
résultat = 1 / x
except Exception:
pass # Avale silencieusement toutes les erreurs !
# ✓ Attraper spécifiquement ce que l'on sait gérer
try:
résultat = 1 / x
except ZeroDivisionError:
résultat = 0.0
```{code-cell} python
# Anti-patterns en action — comparaisons
# ✗ Comparer à None avec ==
def anti_pattern_none(x):
if x == None: return "nul" # Wrong
return "non nul"
# ✓ Comparer à None avec is / is not
def idiomatique_none(x):
if x is None: return "nul"
return "non nul"
# ✗ Utiliser range(len(...)) pour itérer
fruits = ["pomme", "poire", "cerise"]
for i in range(len(fruits)): # Non idiomatique
print(f" {i}: {fruits[i]}")
# ✓ Utiliser enumerate
for i, fruit in enumerate(fruits): # Idiomatique
print(f" {i}: {fruit}")
# ✗ Construire un dict depuis deux listes avec une boucle
noms_anti = ["a", "b", "c"]
vals_anti = [1, 2, 3]
d_anti = {}
for i in range(len(noms_anti)):
d_anti[noms_anti[i]] = vals_anti[i]
# ✓ Utiliser dict(zip(...))
d_idiom = dict(zip(noms_anti, vals_anti))
print(d_anti, d_idiom) # Même résultat
# Tirer parti de la vérité implicite des conteneurs
def compter_pairs(nombres):
"""Compte les pairs, retourne 0 si la liste est vide."""
if not nombres: # Vérifie si la liste est vide — pythonique
return 0
return sum(1 for x in nombres if x % 2 == 0)
print(compter_pairs([]))
print(compter_pairs([1, 2, 3, 4, 5]))
# Utiliser get() sur les dictionnaires plutôt que tester la clé
config = {"debug": True, "port": 8080}
# ✗ Lourd
if "timeout" in config:
timeout = config["timeout"]
else:
timeout = 30
# ✓ Idiomatique
timeout = config.get("timeout", 30)
print(f"timeout = {timeout}")
# setdefault : initialiser une clé seulement si elle n'existe pas
groupes: dict = {}
for item in ["a", "b", "a", "c", "b", "a"]:
groupes.setdefault(item, []).append(1)
comptages = {k: sum(v) for k, v in groupes.items()}
print(comptages)
0
2
timeout = 30
{'a': 3, 'b': 2, 'c': 1}
Visualisation des idiomes pythoniques#
Résumé#
Ce dernier chapitre de synthèse a rassemblé les pratiques et idiomes qui définissent le style pythonique :
Le Zen de Python (
import this) est la philosophie de conception du langage : clarté, explicitation, simplicité et une seule façon évidente de faire les choses.Les conventions PEP 8 définissent
snake_casepour les fonctions et variables,PascalCasepour les classes,SCREAMING_SNAKE_CASEpour les constantes, et les préfixes_et__pour signaler les détails d’implémentation.Les compréhensions de listes, de dictionnaires et d’ensembles sont le moyen idiomatique de construire des collections par transformation et filtrage. Les expressions génératrices (
(...)) offrent la même expressivité en mode paresseux.L”unpacking distribue les éléments d’un itérable dans plusieurs variables, simplifie les échanges de valeurs, et s’intègre naturellement dans les boucles avec
enumerateetzip.Les f-strings sont le mécanisme standard de formatage : elles supportent les spécifications de format, le débogage avec
=, et toute expression Python arbitraire. L”opérateur walrus (:=) évite les calculs redondants dans les conditions et les boucles.L’instruction
match/casepermet un filtrage structurel puissant sur des tuples, des dictionnaires, des types et des valeurs littérales.pathlib.Pathremplace avantageusementos.pathavec une API orientée objet pour la manipulation des chemins.Le module
collectionsfournitCounter(comptages),defaultdict(valeur par défaut automatique),namedtuple(tuples avec champs nommés) etdeque(file double-terminée avec taille maximale optionnelle).Le module
contextlibpermet de créer des gestionnaires de contexte avec@contextmanager, et proposesuppressetredirect_stdoutpour des besoins courants.Les anti-patterns à éviter incluent : la comparaison explicite à
True/Falseou àNoneavec==, la construction de listes inutiles avant itération, la concaténation de chaînes en boucle, et la capture trop large des exceptions.