Public cible
Débutants, étudiants et professionnels qui souhaitent acquérir une pratique structurée dans ce domaine.
Écrire des programmes Python lisibles, du premier print() au projet testé : syntaxe, fonctions, POO, collections, fichiers, gestion d'erreurs et tests avec la bibliothèque standard.
Débutants, étudiants et professionnels qui souhaitent acquérir une pratique structurée dans ce domaine.
Comprendre les notions essentielles et les appliquer dans un projet vérifiable.
Aucune connaissance préalable n’est strictement obligatoire ; les prérequis détaillés sont précisés dans les ateliers.
Tous niveaux
La validation repose sur les évaluations actives et les livrables pratiques du cours.
Un ordinateur, un navigateur et les outils indiqués dans le premier atelier sont suffisants pour commencer.
Écrire et tester des fonctions Python en respectant PEP 8.
Obligatoire ConsulterÉcrire et tester des fonctions Python en respectant PEP 8.
Obligatoire ConsulterÉcrire et tester des fonctions Python en respectant PEP 8.
Obligatoire ConsulterÉcrire et tester des fonctions Python en respectant PEP 8.
Obligatoire ConsulterÉcrire et tester des fonctions Python en respectant PEP 8.
Obligatoire ConsulterDébutants en programmation et developers venant d'un autre langage qui oublient les réflexes propres à Python. Aucun prérequis en algorithmique n'est demandé, seulement l'envie de s'entraîner.
40 heures, en modules courts. Chaque module se termine par des exercices corrigés et un atelier.
Python est un langage interprété, à indentation significative et à typage dynamique. L'indentation ne sert pas qu'à la lisibilité : elle détermine les blocs.
D'après la page officielle de téléchargement (relevée le 28/09/2026) :
def saluer(nom: str) -> str:
return f"Bonjour, {nom} !"
print(saluer("Alice"))
Un fichier Python contient des fonctions et des
instructions de premier niveau. Le point d'entrée
classique est if __name__ == "__main__":, qui
permet d'importer le fichier sans exécuter le code.
snake_casePascalCaseMAJUSCULESPEP 8 est la référence de style communautaire. Suivre cette convention, c'est écrire du Python que les autres comprendront sans commentaire.
Pour s'exercer immédiatement, l'interpréteur en ligne officiel permet de exécuter du code dans le navigateur.
Sur macOS et Linux, le gestionnaire de paquets du système installe Python. Sur Windows, on installe le paquet officiel. Le principe est le même partout : ensuite, on utilise un environnement virtuel pour isoler les dépendances.
# créer un environnement, puis l'activer
python3 -m venv .venv
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows
# installer une bibliothèque dans cet environnement uniquement
pip install requests
Pourquoi un environnement virtuel ? Un projet peut demander la version 1 d'une bibliothèque et un autre la version 2. L'environnement virtuel garde les deux séparés. C'est la pratique attendue sur tout projet autre qu'un simple essai.
python3 mon_script.py
Avec les f-strings, l'affichage se fait directement dans la chaîne :
nom, age = "Alice", 34
print(f"{nom} aura {age + 1} ans l'an prochain")
Python est à typage dynamique : le type est déduit à la
volée, il n'y a pas de déclaration de type. L'annotation
(nom: str) est une aide à la lecture et aux
outils, pas une contrainte d'exécution.
entier = 42
flottant = 3.14
texte = "Python"
booleen = True # True / False, avec une majuscule
rien = None # absence de valeur, ce n'est pas 0 ni ""
print(type(entier)) # <class 'int'>
print(type(rien)) # <class 'NoneType'>
int, float, complex — numériquesstr — texte, immuablebool — vrai ou fauxlist, tuple, set, dict — collectionsNoneType — la valeur NoneEn Python, 0, "", [],
{} et None sont faux. Une
liste vide est donc fausse, ce qui surprend au premier
abord mais évite les tests de longueur everywhere.
if valeurs: # faux si la liste est vide
print("il y a des valeurs")
# explicite et plus lisible quand le type compte
if len(valeurs) > 0:
print("il y a des valeurs")
nom = "Alice"
print(nom.upper()) # ALICE (renvoie une nouvelle chaîne)
print(nom[0]) # A
print(nom[-1]) # e
print(nom[0:3]) # Ali (découpage)
print(nom.upper() == "ALICE") # True
if note >= 16:
mention = "Très bien"
elif note >= 14:
mention = "Bien"
elif note >= 10:
mention = "Passable"
else:
mention = "Insuffisant"
Python n'a pas de switch : on utilise
match (disponible depuis 3.10) quand on compare
une même valeur à plusieurs cas, sinon des
if/elif.
def Notes(note: int) -> str:
match note:
case 20:
return "Parfait"
case 19 | 18:
return "Excellent"
case n if n < 10:
return "Insuffisant"
case _:
return "Correct"
for lettre in "Python":
print(lettre)
for index, valeur in enumerate(["a", "b", "c"]):
print(index, valeur)
compteur = 0
while compteur < 3:
print(compteur)
compteur += 1
break sort de la boucle, continue
passe à l'itération suivante. else après une
boucle s'exécute si aucun break n'a eu lieu —
une particularité qui surprend les débutants mais qui
exprime bien l'intention « si tout s'est terminé
normalement ».
La compréhension de liste construit une liste à partir
d'un itérable. C'est l'idiomate dominant en Python : plus
court, et souvent plus rapide qu'une boucle avec
append.
nombres = [1, 2, 3, 4, 5]
carres = [n * n for n in nombres] # [1, 4, 9, 16, 25]
pairs = [n for n in nombres if n % 2 == 0] # [2, 4]
# avec transformation et condition
etiquettes = [f"#{n}" for n in nombres if n > 3] # ['#4', '#5']
longueurs = {mot: len(mot) for mot in ["chat", "chien"]}
# {'chat': 4, 'chien': 5}
initiales = {mot[0] for mot in ["chat", "chien", "cheval"]} # {'c'}
Si vous n'avez besoin que de parcourir la valeur sans la stocker, utilisez une expression génératrice : elle ne construit rien en mémoire.
carres = (n * n for n in nombres) # pas de crochets : paresseux
for c in carres:
print(c)
Si la logique dépasse une ligne, ou si elle contient une
boucle for imbriquée, la compréhension devient
illisible : une boucle explicite est alors plus claire.
mots = ["Python", "est", "simple", "et", "puissant"]
resultat = []
for m in mots:
if len(m) > 3:
resultat.append(m.upper())En Python, une fonction est définie avec def,
les paramètres par défaut sont évalués à la définition, et la valeur de
retour est return — un return
sans valeur arrête la fonction et renvoie
None.
def moyenne(notes: list[float], mention: str = "") -> float:
"""Calcule la moyenne et renvoie un message."""
if not notes: # évite la division par zéro
raise ValueError("liste de notes vide")
moyenne = sum(notes) / len(notes)
if mention:
return moyenne # type ignore
return moyenne, mention # un tuple
# MAUVAIS : la liste est créée une seule fois, partagée par tous les appels
def ajouter(elt, cible=[]):
cible.append(elt)
return cible
# BON : None par défaut, liste créée à chaque appel
def ajouter(elt, cible=None):
if cible is None:
cible = []
cible.append(elt)
return cible
def afficher(nom, *, couleur="noir", taille=10):
print(nom, couleur, taille)
afficher("titre", couleur="rouge") # lisible, l'ordre n'importe plus
premier, *reste, dernier = [1, 2, 3, 4, 5]
# premier=1, reste=[2,3,4], dernier=5
Le * seul rend les paramètres suivants
obligatoires et nommés : c'est une façon simple d'éviter
les erreurs d'appel.
carre = lambda x: x * x # une seule expression
def multiplier(facteur):
def interne(x):
return x * facteur # « capture » du facteur
return interne
double = multiplier(2)
print(double(21)) # 42
Une classe décrit des données et les méthodes qui les
manipulent. Le constructeur est __init__, et
la méthode __repr__ définit ce qui s'affiche.
class CompteBancaire:
"""Compte avec solde contrôlé."""
def __init__(self, titulaire: str, solde: float = 0.0):
self.titulaire = titulaire
self._solde = solde # convention : attribut « privé »
def deposer(self, montant: float) -> bool:
if montant <= 0:
return False
self._solde += montant
return True
@property # accès en lecture
def solde(self) -> float:
return self._solde
def __repr__(self) -> str:
return f"Compte({self.titulaire}, {self._solde:.2f})"
@property transforme une méthode en
attribut en lecture seule : c.solde au lieu de
c.get_solde(), tout en gardant le contrôle. Une
classe dataclass évite d'écrire le constructeur
à la main :
from dataclasses import dataclass, field
@dataclass
class Etudiant:
nom: str
age: int
cours: list[str] = field(default_factory=list) # pas de partage !
e = Etudiant("Alice", 22, ["Python"])
print(e) # Etudiant(nom='Alice', age=22, cours=['Python'])
class Forme:
def aire(self) -> float:
raise NotImplementedError("à redéfinir")
class Cercle(Forme):
def __init__(self, rayon: float):
self.rayon = rayon
def aire(self) -> float:
return math.pi * self.rayon ** 2
formes = [Cercle(1), Cercle(2)]
print(sum(f.aire() for f in formes)) # la même méthode, des résultats différents
La dernière ligne est le polymorphisme : le code ne
connaît pas le type réel, il appelle aire().
mon_projet/
├── main.py
├── outils/
│ ├── __init__.py
│ └── maths.py
from outils.maths import moyenne # une chose précise
import outils.maths # le module entier
from outils import maths # le sous-module
On importe un symbole précis plutôt qu'un module entier : on sait d'où vient chaque chose, et on évite les collisions de noms.
Avant d'écrire une fonction, vérifier si elle existe
déjà : math, datetime,
json, pathlib, csv,
random, itertools,
collections, unittest.
import json
with open("config.json", encoding="utf-8") as f:
config = json.load(f) # dict
from pathlib import Path
for fichier in Path("dossier").glob("*.txt"):
print(fichier)
Un décorateur est une fonction qui enveloppe une autre fonction. Il sert à la journalisation, aux mesures de temps, aux permissions.
import functools
import time
def chronometrer(fonction):
@functools.wraps(fonction) # garde le nom et la docstring
def enveloppe(*args, **kwargs):
debut = time.perf_counter()
try:
return fonction(*args, **kwargs)
finally:
duree = (time.perf_counter() - debut) * 1000
print(f"{fonction.__name__} : {duree:.1f} ms")
return enveloppe
@chronometrer
def traitement():
...
def compter_appels(fonction):
appels = [0]
def enveloppe(*args, **kwargs):
appels[0] += 1
return fonction(*args, **kwargs)
return enveloppeArticle
(nom, prix, quantité) avec une méthode
total(), et une fonction qui renvoie le
total d'une liste d'articles.journalise qui affiche les arguments reçus
avant l'appel.Ces quatre structures couvrent l'essentiel des besoins. Choisir la bonne, c'est déjà une bonne part de la qualité du code.
# liste : ordonnée, modifiable
nombres = [3, 1, 2]
nombres.append(4) # [3, 1, 2, 4]
nombres.sort() # [1, 2, 3, 4]
nombres.remove(1)
# tuple : ordonné, NON modifiable
coordonnees = (48.85, 2.35)
# ensemble : sans doublon, non ordonné
tags = {"python", "web", "python"} # {'python', 'web'}
# dictionnaire : clé → valeur
stock = {"clavier": 12}
stock["souris"] = 40
stock.get("ecran", 0) # 0 au lieu d'une erreur
| Besoin | Structure |
|---|---|
| Liste ordonnée, modifiée | list |
| Valeurs fixes | tuple |
| Sans doublon, test d'appartenance rapide | set |
| Accès par clé | dict |
premier, second = [10, 20]
cle, valeur = ("a", 1)
notes = [("alice", 15), ("bob", 12), ("alice", 18)]
mieux = {}
for nom, note in notes:
if nom not in mieux or note > mieux[nom]:
mieux[nom] = note
Ces deux modules de la bibliothèque standard remplacent beaucoup de boucles manuelles, en plus lisible et plus rapide.
import itertools
nombres = [1, 2, 3, 4, 5]
list(itertools.chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(itertools.combinations(["a", "b", "c"], 2)) # [('a','b'), ('a','c'), ('b','c')]
list(itertools.groupby(sorted(["a", "ab", "b"]))) # groupes de valeurs voisines
import functools
f = functools.reduce(lambda acc, x: acc * x, [1, 2, 3, 4]) # 24
total = functools.partial(sum, [1, 2, 3]) # sum([1,2,3], ...)
Attention à groupby : il ne groupe que
les éléments consécutifs. Il faut trier avant, sinon
le résultat est faux.
for index, valeur in enumerate(["a", "b"], start=1):
print(index, valeur) # 1 a / 2 b
for nom, note in zip(["alice", "bob"], [15, 12]):
print(nom, note)
eleves = [("bob", 12), ("alice", 18), ("carol", 12)]
# par note décroissante, puis par nom
eleves.sort(key=lambda e: (-e[1], e[0]))
On lit et on écrit avec open(), toujours dans
un bloc with, qui ferme le fichier même en cas
d'erreur.
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Alice 15\n")
with open("notes.txt", encoding="utf-8") as f:
for ligne in f:
nom, note = ligne.strip().split()
print(nom, int(note))
Les modes utiles : r lecture, w
écriture (écrase), a ajout, x
écriture qui échoue si le fichier existe.
encoding="utf-8" n'est pas facultatif : sans
lui, la lecture dépend de la configuration système et peut
échouer sur un accents selon la machine.
from pathlib import Path
dossier = Path("data")
dossier.mkdir(exist_ok=True) # ne lève pas si le dossier existe
chemin = dossier / "notes.txt"
chemin.write_text("contenu", encoding="utf-8")
for fichier in dossier.glob("*.txt"):
print(fichier.name, fichier.stat().st_size)
import csv
import json
with open("clients.csv", newline="", encoding="utf-8") as f:
lecteur = csv.DictReader(f) # première ligne = en-têtes
for ligne in lecteur:
print(ligne["nom"], ligne["ville"])
with open("config.json", "w", encoding="utf-8") as f:
json.dump({"debug": True}, f, indent=2)
with open("config.json", encoding="utf-8") as f:
config = json.load(f)
newline="" est requis par csv :
sans lui, les retours à la ligne sont mal interprétés sur
certains systèmes.
contacts.csv (nom, email), compter les
doublons d'emails et écrire les doublons dans un autre
fichier.paires = []
for i in range(len(nombres)):
for j in range(i + 1, len(nombres)):
paires.append((nombres[i], nombres[j]))Un programme échoue : on ne supprime pas l'erreur, on la
traite au bon endroit. try/except sert à
anticiper un échec attendu, pas à tout attraper.
def lire_nombre():
saisie = input("Un nombre : ")
try:
return int(saisie)
except ValueError:
print("Ce n'est pas un nombre entier, recommencez.")
return None
# TROP LARGE : masque les vraies erreurs de logique
try:
traitement(utilisateur)
except Exception:
pass
# JUSTE : on ne traite que l'échec prévu
try:
resultat = 10 / diviseur
except ZeroDivisionError:
resultat = None
def retirer(compte, montant):
if montant <= 0:
raise ValueError("le montant doit être positif")
if montant > compte.solde:
raise ValueError("solde insuffisant")
compte.deposer(-montant)
raise signale une situation impossible à
contourner. Dans une bibliothèque, on laisse remonter
l'exception : l'appelant décidera.
fichier = None
try:
fichier = open("data.txt", encoding="utf-8")
contenu = fichier.read()
except FileNotFoundError:
print("fichier absent")
else:
print(f"{len(contenu)} caractères")
finally:
if fichier is not None:
fichier.close()
En pratique, le with remplace ce
try/finally et gère aussi l'echec. Et un
objet qui se ferme tout seul peut être écrit avec
__enter__ et __exit__.
class SoldeInsuffisant(Exception):
def __init__(self, manquant: float):
self.manquant = manquant
super().__init__(f"il manque {manquant:.2f}")
Un test vérifie une seule chose et échoue clairement. La
bibliothèque standard unittest suffit, sans
dépendance supplémentaire.
import unittest
class TestCompteBancaire(unittest.TestCase):
def setUp(self):
# exécuté avant chaque test : on repart d'un état propre
self.compte = CompteBancaire("Alice", 100.0)
def test_depot_ajoute_au_solde(self):
self.compte.deposer(50.0)
self.assertEqual(self.compte.solde, 150.0)
def test_montant_negatif_refuse(self):
self.assertFalse(self.compte.deposer(-10.0))
self.assertEqual(self.compte.solde, 100.0)
def test_solde_vide_leve_une_erreur(self):
with self.assertRaises(ValueError):
moyenne([])
assertEqual, assertTrue,
assertIs (identité, pour None)assertIn, assertAlmostEqual
pour les flottantsassertRaises quand on attend une erreurtest_montant_negatif_refuse).python -m unittest discover -v
python -m unittest tests.test_calculs
vers_entier(texte) qui renvoie un entier ou
None, et n'utilisez jamais
except: seul.Fichier utilisable avec with,
qui ouvre le fichier et le ferme automatiquement.Le projet porte sur tout le cours. Il est volontairement modeste en dépendances — seule la bibliothèque standard — et Livrable avec des tests.
Un gestionnaire de dépenses personnelles : ajouter des dépenses, les classer par catégorie, produire un rapport mensuel, et persister les données dans un fichier JSON.
depenses.py pour le modèle et
les règles métier, un fichier test_depenses.py
pour les tests, un point d'entrée main.py.json.dump et relues au démarrage.gestionnaire/
├── main.py
├── depenses.py
└── test_depenses.py
| Étape | Livrable | Critère de fin |
|---|---|---|
| 1. Modèle de données | classe Depense | tests unitaires au vert |
| 2. Métier | ajout, filtrage, rapport | tests unitaires au vert |
| 3. Persistance | lecture/écriture JSON | aller-retour vérifié |
| 4. Interface | menu en console | parcours complet testé |
| 5. Qualité | PEP 8, docstrings | relecture sans commentaire |
On ne passe à l'étape suivante que quand la précédente est vérifiée. C'est ce qui distingue un projet qui avance d'un projet qui s'épuise.
except silencieux.snake_case pour les fonctions et
variables, PascalCase pour les classespython -m venv et pip pour
l'environnement et les dépendancesunittest pour les tests (inclus)pip install ruff : vérificateur de style
et de bogues, très rapidepip install black : formatage
automatiqueUn projet sans requirements.txt n'est pas
reproductible : la liste des dépendances fait partie du
code.
pandas pour l'analyse,
matplotlib pour les graphiques.Flask (léger) ou
Django (complet).openpyxl pour Excel,
requests pour les API.pytest, alternative
moderne à unittest.Reprendre un script existant et le rendre lisible par quelqu'un d'autre : nommer, decomposer, tester.
Publié le 28/09/2026