def additionner(a: int, b: int) -> int:
"""Additionne deux entiers."""
return a + b
# Python ignore les annotations à l'exécution — aucun check à l'appel
print(additionner(3, 5))8
Un code qui marche n’est qu’un début. Un code de qualité est lisible, cohérent, maintenable. La communauté Python a établi des conventions (PEP 8) et des outils automatiques (Black, Flake8, Ruff, mypy) qui vous aident à écrire comme un professionnel. C’est un sujet qui compte au TOSA Expert — et qui change la vie en entreprise.
Trois constats :
La PEP 8 est le guide de style officiel. Les outils modernes l’appliquent automatiquement.
La PEP 8 est longue. Voici les règles que vous devez connaître par cœur.
# ✅
def f():
if condition:
action()Pas de tabulations. Configurez votre éditeur pour que Tab insère 4 espaces.
# ✅ Couper les longues lignes
resultat = fonction_avec_long_nom(
argument_1,
argument_2,
argument_3,
)La PEP 8 stricte dit 79. Black (voir plus bas) utilise 88 par défaut, considéré comme un compromis moderne.
| Élément | Style | Exemple |
|---|---|---|
| Variable, fonction, méthode | snake_case |
ma_variable, calculer_moyenne() |
| Classe | PascalCase |
CompteBancaire |
| Constante | UPPER_SNAKE_CASE |
MAX_ESSAIS, PI |
| Privé (convention) | préfixe _ |
_solde, _methode_interne |
| Module/package | lowercase, court | mon_module, utils |
# ✅ Autour des opérateurs
x = 1 + 2
y = a * b - c
# ✅ Après une virgule
def f(a, b, c):
...
# ❌ Pas d'espaces autour des = pour valeurs par défaut
def f(x=10): ... # ✅
def f(x = 10): ... # ❌
# ✅ Pas d'espace à l'intérieur des parenthèses
f(x, y) # ✅
f( x, y ) # ❌# Deux lignes vides entre les fonctions/classes top-level
def fonction_1():
...
def fonction_2():
...
# Une ligne vide entre les méthodes d'une classe
class MaClasse:
def methode_1(self):
...
def methode_2(self):
...# ✅ Un import par ligne
import os
import sys
# ❌ À éviter
import os, sys
# Ordre des imports (séparés par des lignes vides) :
# 1. Bibliothèque standard
import os
from pathlib import Path
# 2. Bibliothèques tierces
import requests
import numpy as np
# 3. Modules locaux
from .utils import fonction# ✅ Utilisez is avec None
if x is None:
...
# ❌
if x == None:
...
# ✅ Négation claire
if not x:
...
# ❌ Double négation
if not x != 10:
...Les guillemets simples et doubles sont équivalents. Choisissez-en un et tenez-vous-y. Black utilise "..." par défaut.
# Cohérence : tous simples ou tous doubles
nom = "Alice"
salutation = "Bonjour"
# Double si la chaîne contient un simple
citation = "Il a dit : 'salut'"Pour chaque module, classe, fonction publique, écrivez une docstring (vu chapitre 2 partie 2).
def calculer_moyenne(notes):
"""Calcule la moyenne d'une liste de notes.
Arguments:
notes: liste de nombres (int ou float).
Renvoie:
La moyenne arithmétique, ou 0 si la liste est vide.
"""
if not notes:
return 0
return sum(notes) / len(notes)# ❌ Inutile : le code dit exactement ça
x = x + 1 # on ajoute 1 à x
# ✅ Utile : explique le "pourquoi"
x = x + 1 # on compense l'offset de l'index 0-basedBlack est un formateur de code automatique. Vous exécutez black mon_fichier.py, et votre code est reformaté selon des règles fixes et non négociables.
pip install black# Formater un fichier
black mon_script.py
# Formater tout un dossier
black .
# Vérifier sans modifier
black --check .
# Voir les changements sans les appliquer
black --diff mon_script.pyCode mal formaté :
def calculer_total( prix, quantite ,tva = 0.20 ):
return prix*quantite*( 1+tva )
liste=[1 ,2,3, 4,5]Après black :
def calculer_total(prix, quantite, tva=0.20):
return prix * quantite * (1 + tva)
liste = [1, 2, 3, 4, 5]Black se résume en une phrase : « toutes les discussions sur le style sont terminées ».
Plus de débats pour savoir s’il faut mettre un espace ici ou là, aligner les signes =, préférer les simples ou doubles guillemets. Black décide, on ne discute pas, on code.
Cette approche économise un temps fou en équipe et en revue de code.
Un linter analyse votre code pour détecter :
Historiquement, le plus populaire.
pip install flake8
flake8 mon_script.pyExemple de sortie :
mon_script.py:3:1: F401 'os' imported but unused
mon_script.py:10:80: E501 line too long (85 > 79 characters)
mon_script.py:15:5: E225 missing whitespace around operator
Codes :
pyflakes).Ruff (écrit en Rust) est 10 à 100 fois plus rapide que Flake8 et combine plusieurs outils. C’est l’outil en ascension dans les projets modernes.
pip install ruff
ruff check mon_script.py # lint
ruff format mon_script.py # formatage (comme Black)Plus strict et plus verbeux que Flake8. Vérifie aussi des aspects comme la longueur des fonctions, la complexité, la documentation.
pip install pylint
pylint mon_script.pyPylint donne un score sur 10 à votre code — parfois décourageant au début, toujours instructif.
| Outil | Rôle | Vitesse |
|---|---|---|
| Black | Formatage auto | Rapide |
| Ruff format | Formatage auto (Black-compatible) | Ultra-rapide |
| Flake8 | Lint (style + erreurs) | Moyen |
| Ruff check | Lint (multi-outils) | Ultra-rapide |
| Pylint | Lint strict | Lent |
| mypy | Vérification de types | Moyen |
| isort | Tri des imports | Rapide |
Setup moderne minimal : Ruff (format + check) + mypy si vous utilisez les types.
Python est dynamiquement typé : on n’annonce pas les types. Mais on peut les annoter pour la documentation et le contrôle statique.
def additionner(a: int, b: int) -> int:
"""Additionne deux entiers."""
return a + b
# Python ignore les annotations à l'exécution — aucun check à l'appel
print(additionner(3, 5))8
Vous verrez la typologie complète au chapitre suivant (Type hints et mypy).
Pour l’instant retenez : a: int signale qu’on attend un int. mypy peut vérifier cela sans exécuter le code.
Au-delà de la PEP 8, il y a le « Pythonique » — des façons d’écrire idiomatiques.
# ❌ Verbeux
pairs = []
for n in range(10):
if n % 2 == 0:
pairs.append(n)
# ✅ Pythonique
pairs = [n for n in range(10) if n % 2 == 0]enumerate plutôt qu’un compteur manuel# ❌
i = 0
for item in liste:
print(i, item)
i += 1
# ✅
for i, item in enumerate(liste):
print(i, item)zip pour itérer sur plusieurs listesnoms = ["Alice", "Bob", "Eve"]
ages = [30, 25, 28]
# ❌
for i in range(len(noms)):
print(noms[i], ages[i])
# ✅
for nom, age in zip(noms, ages):
print(nom, age)with pour les ressources# ❌
f = open("fichier.txt")
# ... traitement
f.close()
# ✅
with open("fichier.txt") as f:
# ... traitementdict.get plutôt qu’un try/except# ❌ Verbeux
try:
x = d["cle"]
except KeyError:
x = "défaut"
# ✅ Pythonique
x = d.get("cle", "défaut")in plutôt qu’une chaîne de or# ❌
if code == 200 or code == 201 or code == 204:
...
# ✅
if code in (200, 201, 204):
...% et .formatnom, age = "Alice", 30
# ❌ Anciens styles
msg = "Bonjour %s, %d ans" % (nom, age)
msg = "Bonjour {}, {} ans".format(nom, age)
# ✅ f-string (Python 3.6+)
msg = f"Bonjour {nom}, {age} ans"Python a une philosophie officielle, connue sous le nom de Zen of Python — écrit par Tim Peters. Affichez-le :
import thisThe 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éditer :
Ces principes guident les choix de conception de Python et de ses bibliothèques. Ils devraient aussi guider les vôtres.
Voici ce qu’un projet pro met en place :
Pre-commit hooks (Git) : Ruff + Black exécutés avant chaque commit. Le code non conforme est refusé.
CI/CD : le pipeline d’intégration continue lance les linters et tests à chaque push.
Revues de code : un collègue relit avant le merge.
Tests unitaires : voir chapitre 7 — chaque bug trouvé donne un test pour ne pas le reproduire.
Setup minimal avec pre-commit :
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-formatPuis :
pip install pre-commit
pre-commit install
# Désormais, chaque git commit vérifie et formateVoyons un morceau de code mauvais, puis sa version améliorée.
def calcDuCoutTOTAL(l,t=0.2):
r=0
for i in range(len(l)):
r=r+l[i]['prix']*l[i]['qte']
return r*(1+t)Problèmes :
l, t, r, calcDuCoutTOTAL.def.for i in range(len(l)) au lieu d’itérer directement.l[i]['prix'] au lieu d’utiliser une variable.def calculer_cout_total(articles, tva=0.20):
"""Calcule le coût TTC d'un panier d'articles.
Arguments:
articles: liste de dicts avec les clés 'prix' et 'qte'.
tva: taux de TVA (défaut 20%).
Renvoie:
Le total TTC sous forme de float.
"""
total_ht = sum(article["prix"] * article["qte"] for article in articles)
return total_ht * (1 + tva)
# Test
panier = [
{"prix": 10, "qte": 2},
{"prix": 5, "qte": 3},
]
print(f"Total TTC : {calculer_cout_total(panier):.2f} €")Total TTC : 42.00 €
Améliorations :
range(len(...))).sum() — concis et efficace.Même logique, code 10 fois plus lisible.
Quel est le style de nommage recommandé par PEP 8 pour les fonctions ?
camelCasesnake_casePascalCaseUPPER_SNAKE_CASEb) snake_case — les fonctions et variables en minuscules avec underscores (calculer_moyenne, ma_variable). Les classes en PascalCase, les constantes en UPPER_SNAKE_CASE.
Combien d’espaces pour l’indentation selon PEP 8 ?
b) 4 espaces — pas de tabulations, cohérence dans tout le code.
Que fait Black ?
b) — Black est un formateur (pas un linter) qui applique un style non négociable. Élimine les débats de style.
Quelle expression est la plus Pythonique ?
for i in range(len(liste)): print(liste[i])i = 0; while i < len(liste): print(liste[i]); i += 1for item in liste: print(item)liste.map(print)c) — itérer directement sur la liste. Si on a besoin de l’index, enumerate(liste).
Que recommande PEP 8 pour comparer avec None ?
if x == None:if x is None:if x == null:b) if x is None: — None est un singleton, utiliser is est idiomatique et recommandé.
Quelle est la différence entre Black et Flake8 ?
b) — Black = formateur (transforme), Flake8 = linter (signale seulement). Utilisés ensemble : Black pour le style, Flake8 pour les erreurs logiques.
Parmi ces expressions, laquelle est la plus Pythonique ?
if statut == "OK" or statut == "VALID" or statut == "DONE":
...if statut in ("OK", "VALID", "DONE"):if any(statut == s for s in ("OK", "VALID", "DONE")):match/caseb) if statut in ("OK", "VALID", "DONE"): — concis et clair. L’option c) fonctionne mais est plus lourde. match/case est overkill pour ce test simple.
Qu’affiche import this ?
c) — easter egg classique. import this affiche 19 aphorismes guidant la philosophie Python.
Réécrivez ces identifiants selon les conventions PEP 8.
| Original | Rôle |
|---|---|
ListeDesClients |
variable |
calculTotal |
fonction |
compte_bancaire |
classe |
max_essais |
constante |
__variable_privee__ |
attribut privé |
| Original | Rôle | Correct |
|---|---|---|
ListeDesClients |
variable | liste_des_clients |
calculTotal |
fonction | calcul_total |
compte_bancaire |
classe | CompteBancaire |
max_essais |
constante | MAX_ESSAIS |
__variable_privee__ |
attribut privé | _variable_privee (pas double sauf dunder) |
Les __nom__ (double de chaque côté) sont réservés à Python (dunders : __init__, __str__). N’en créez pas.
Le __nom (double au début seulement) déclenche le name mangling — usage rare et avancé.
Le _nom (simple underscore au début) est la convention usuelle pour « privé ».
Voici un code fonctionnel mais peu qualitatif. Refactorisez-le.
def f(L,x):
res=[]
for i in range(len(L)):
if L[i]>x:
res.append(L[i])
if len(res)==0: return None
return res
print(f([1,5,10,2,15,8],7))Appliquez :
def elements_superieurs_a(valeurs, seuil):
"""Renvoie les éléments strictement supérieurs au seuil.
Arguments:
valeurs: liste de nombres à filtrer.
seuil: valeur plancher (exclue).
Renvoie:
La liste des éléments retenus, ou None si elle est vide.
"""
retenus = [v for v in valeurs if v > seuil]
return retenus or None
# Test
print(elements_superieurs_a([1, 5, 10, 2, 15, 8], 7))
print(elements_superieurs_a([1, 2, 3], 10)) # None[10, 15, 8]
None
f → elements_superieurs_a.L, x → valeurs, seuil.for range(len) + append.retenus or None : idiome Pythonique (si liste vide → None grâce au court-circuit).Passez ce code dans Black : rien à changer. Dans Flake8 : zéro warning.
Transformez ces extraits en versions Pythoniques.
# Code 1
noms = ["Alice", "Bob"]
ages = [30, 25]
for i in range(len(noms)):
print(noms[i] + " a " + str(ages[i]) + " ans")
# Code 2
d = {"a": 1}
if "b" in d:
x = d["b"]
else:
x = 0
# Code 3
liste = []
for n in range(20):
if n % 3 == 0 and n > 5:
liste.append(n ** 2)
# Code 4
if x != None:
if x > 0 and x < 100:
print("OK")# Code 1 : zip + f-string
noms = ["Alice", "Bob"]
ages = [30, 25]
for nom, age in zip(noms, ages):
print(f"{nom} a {age} ans")
# Code 2 : dict.get
d = {"a": 1}
x = d.get("b", 0)
print(x)
# Code 3 : compréhension
liste = [n ** 2 for n in range(20) if n % 3 == 0 and n > 5]
print(liste)
# Code 4 : is not None + comparaison chaînée
x = 50
if x is not None and 0 < x < 100:
print("OK")Alice a 30 ans
Bob a 25 ans
0
[36, 81, 144, 225, 324]
OK
zip pour parcourir en parallèle.dict.get(clé, défaut) au lieu de if in ... else.x is not None (pas != None).0 < x < 100 est lisible et Pythonique.Pour chacun des extraits suivants, dites quel est le problème et proposez une correction.
# Extrait A
lst = [1, 2, 3]
for i in range(0, len(lst), 1):
print(lst[i])
# Extrait B
x = 5
if x == True:
print("ok")
# Extrait C
def f(x):
return True if x > 0 else False
# Extrait D
for k in d.keys():
print(d[k])A — Usage inutile de range(len()) et du step=1.
lst = [1, 2, 3]
for x in lst:
print(x)1
2
3
B — == True est redondant. Un test booléen suffit.
x = 5
if x: # (ou `if bool(x):` si on veut être explicite)
print("ok")
# Si on veut vraiment tester l'égalité stricte à True :
if x is True:
print("c'est exactement True")ok
C — Renvoyer un booléen en utilisant if/else est tautologique.
def f(x):
return x > 0
print(f(5), f(-3))True False
D — Itérer sur un dict donne déjà les clés ; d.keys() est redondant. Et pour itérer sur les valeurs, on a plus direct :
d = {"a": 1, "b": 2}
# Pour les clés
for k in d:
print(k)
# Pour les valeurs
for v in d.values():
print(v)
# Pour les paires
for k, v in d.items():
print(k, v)a
b
1
2
a 1
b 2
snake_case (variables/fonctions), PascalCase (classes), UPPER_SNAKE_CASE (constantes), _private (convention).is None pour comparer à None, pas == None.enumerate, zip, with, dict.get, f-strings, comparaisons chaînées.range(len()), == None, == True, return True if x else False.import this pour le Zen of Python.← Chapitre précédent : sys, os, pathlib • Chapitre suivant : Tests unitaires →