Aller au contenu
Sommaire

Cours Python — le langage : types, fonctions, objets, erreurs, modules, tests

Cours · environ 40 min de lecture

Validé

Validé. Ce site n’exécute aucune commande. 11 points non vérifiés · Sources

Objectifs d'apprentissage

À la fin de ce cours, l'apprenant peut :

  1. expliquer le modèle « noms, objets, références » et prévoir l'effet d'une affectation, d'un alias et d'une copie ;
  2. distinguer objets mutables et immuables, et == de is ;
  3. manipuler les types de base, les chaînes (formatage, découpage) et les quatre conteneurs list, tuple, dict, set ;
  4. écrire des compréhensions et des expressions génératrices lisibles ;
  5. employer if, for, while, break/continue/else et match ;
  6. définir des fonctions (arguments par défaut, mots-clés, *args, **kwargs, portée, lambda) ;
  7. lever, attraper et enchaîner des exceptions ; garantir un nettoyage avec finally et with ;
  8. lire et écrire des fichiers texte avec un encodage explicite ;
  9. organiser du code en modules et en packages, avec if __name__ == "__main__" ;
  10. écrire des classes, de l'héritage et des dataclass ;
  11. écrire des itérateurs, des générateurs et un décorateur simple ;
  12. annoter des fonctions, dire ce que les annotations garantissent (rien à l'exécution) et à quoi sert mypy ;
  13. écrire des tests unittest ;
  14. repérer les pièges classiques (défaut mutable, copie superficielle, identité) ;
  15. écrire du code idiomatique suivant PEP 8 et PEP 20.

Prérequis

  • Savoir ouvrir Terminal, créer un dossier et lire une commande avant de la valider (cours « Terminal macOS »).
  • Avoir lu, ou connaître, le cours « Scripting shell et Python » pour les outils de script (pathlib, argparse, json, subprocess), l'environnement virtuel et Ruff : ce cours ne les reprend pas.
  • Python 3 présent sur le poste (python3) : sa présence et sa version sont non vérifiées. Le cours n'en décrit pas l'installation.
  • Un dossier de test jetable : tous les fichiers du cours y sont créés, jamais ailleurs.
  • Aucun exercice ne demande de droits administrateur ni d'accès au réseau.

Parcours et chapitres

1. Exécuter du Python

Trois façons de lancer du code : l'interpréteur interactif (suite d'instructions saisies une à une), un fichier .py passé à l'interpréteur, ou un module (python3 -m nom). Les exemples du cours sont des fichiers à créer dans le dossier de test, puis lancés avec python3 fichier.py. Les lignes de code Python sont affichées dans des blocs de fichier (bouton Copier) ; les commandes Terminal dans des fenêtres de Terminal.

Terminal
python3 --version
  • Contexte : Terminal ; python3 présent (non vérifié sur le poste de Stéphane).
  • Droits : utilisateur normal.
  • Effet : affiche le numéro de version de l'interpréteur ; ne modifie rien. La forme --version n'a pas été relue dans les pages consultées.
  • Vérification : une ligne du type Python 3.x.y s'affiche ; noter ce numéro, il décide de ce qui est disponible (chapitres 7, 12, 15, 18).
  • Retour arrière : sans objet.
Python
# bonjour.py
print("Bonjour, Python")
Terminal
python3 bonjour.py
  • Contexte : Terminal, dossier de test contenant bonjour.py.
  • Droits : utilisateur normal ; lecture du fichier.
  • Effet : exécute le fichier et affiche Bonjour, Python ; peut créer un dossier __pycache__ (chapitre 11).
  • Vérification : la sortie est Bonjour, Python.
  • Retour arrière : supprimer le dossier de test en relisant son nom.

Attention : exécuter un fichier .py exécute tout ce qu'il contient. Ne lancer que du code relu, jamais un script reçu d'une source non fiable (voir le chapitre 10 pour eval, exec et pickle).

2. Le modèle de données : noms, objets, références

C'est le chapitre le plus important : la plupart des pièges des chapitres suivants en découlent.

  • Tout est objet. Un objet a une identité (qui ne change jamais, « adresse en mémoire » dans la documentation), un type (immuable lui aussi) et une valeur (qui peut changer si l'objet est mutable).
  • Une variable n'est pas une boîte contenant une valeur : c'est un nom qui référence un objet. Plusieurs noms peuvent référencer le même objet (aliasing). La documentation du tutoriel précise que cela a un effet sur les objets mutables (listes, dictionnaires) : la modification est visible par tous les noms.
  • is compare les identités ; id() renvoie un entier qui représente l'identité ; == compare les valeurs.
  • Nombres, chaînes et tuples sont immuables ; listes, dictionnaires et ensembles sont mutables. Un tuple qui contient une liste voit sa « valeur » changer si la liste change.
  • Pour les types immuables, l'interpréteur peut réutiliser un objet existant : après a = 1; b = 1, a et b peuvent ou non désigner le même objet, selon l'implémentation. Ne jamais comparer des nombres ou des chaînes avec is. Après c = []; d = [], la documentation garantit deux listes distinctes.
Trois noms : a et b désignent la même liste, c désigne une autre liste de même valeurnomsobjetsabcliste[1, 2]un seul objetliste[1, 2]a is b : True (même objet)a == c : True, a is c : False
Noms et objets : a et b sont deux noms d'un même objet liste ; c désigne une copie. Valeurs synthétiques ; d'après le tutoriel (classes) et la référence du modèle de données.
Python
# noms.py
a = [1, 2]
b = a
c = list(a)
print(a is b, a == c, a is c)
b.append(3)
print(a, c)
Sortie attendue
True True False[1, 2, 3] [1, 2]
  • Contexte : Python 3 dans un dossier de test ; sortie calculée à la main, non exécutée.
  • Droits : utilisateur normal.
  • Effet : aucun fichier créé (hors __pycache__ éventuel) ; ajouter à b modifie l'objet que a désigne aussi, pas celui de c.
  • Vérification : les deux lignes ci-dessus ; si a is b est faux, relire l'affectation b = a.
  • Retour arrière : sans objet.

À retenir : une affectation (b = a) ne copie jamais l'objet : elle ajoute un nom. Pour une copie, voir le chapitre 19. Passer un objet à une fonction passe aussi une référence : la fonction peut modifier un objet mutable reçu (le tutoriel note que passer un objet est peu coûteux et que l'appelant voit les modifications).

CPython (l'implémentation de référence) libère un objet quand plus rien ne le référence (comptage de références, avec détection retardée des cycles). La documentation précise de ne pas compter sur une libération immédiate : fermer les fichiers explicitement ou avec with (chapitre 10).

3. Types de base et opérateurs

TypeExemplesÀ savoir
int42, -7, 10**30précision illimitée
float3.5, 1e-3généralement un double C ; erreurs d'arrondi (tutoriel, chapitre « Floating-Point Arithmetic », non relu)
complex3 + 4jrare
boolTrue, Falsesous-classe de int : True + 1 vaut 2
NoneTypeNonel'absence de valeur ; se teste avec is None (PEP 8)
str"texte"séquence immuable de points de code Unicode

Opérateurs usuels (formes courantes, non relues ligne à ligne dans les pages consultées) : + - *, / division vraie (toujours un float), // division entière (arrondi vers le bas), % reste, ** puissance.

Python
# nombres.py
print(7 / 2, 7 // 2, -7 // 2, 7 % 2, 2**10)
print(0.1 + 0.2 == 0.3)
print(True + 1, isinstance(True, int))
Sortie attendue
3.5 3 -4 1 1024False2 True
  • Contexte : Python 3 ; sortie calculée à la main, non exécutée.
  • Droits : utilisateur normal.
  • Effet : affiche trois lignes, ne modifie rien.
  • Vérification : comparer aux trois lignes. -7 // 2 donne -4 (arrondi vers le bas, pas vers zéro).
  • Retour arrière : sans objet.

Attention : ne jamais comparer deux float avec == quand ils viennent d'un calcul (0.1 + 0.2 == 0.3 est faux). Pour de l'argent, éviter les float ; le module decimal existe (non consulté, hors périmètre de ce cours).

Valeur de vérité. Sont faux : None, False, le zéro de tout type numérique, et les séquences ou collections vides ('', (), [], {}, set(), range(0)). Tout le reste est vrai par défaut. Les opérateurs and et or court-circuitent et renvoient l'un de leurs opérandes : x or y vaut x s'il est vrai, sinon y. Les comparaisons s'enchaînent : a < b == c. L'opérateur := (affectation dans une expression) existe.

4. Chaînes de caractères

Une str est immuable : toute « modification » produit une nouvelle chaîne. On les écrit entre '…', "…" ou """…""" (plusieurs lignes). L'indexation commence à 0 ; un indice négatif compte depuis la fin ; une tranche s[a:b:pas] exclut b.

Python
# chaines.py
nom = "Python"
print(nom[1:4], nom[-1], nom[::2], len(nom))
print("a-b-c".partition("-"))
print(", ".join(["un", "deux", "trois"]))
print("  espaces  ".strip().upper())
produit, prix = "cahier", 3.5
print(f"{produit:>10} | {prix:6.2f} | {prix=}")
Sortie attendue
yth n Pto 6('a', '-', 'b-c')un, deux, troisESPACES    cahier |   3.50 | prix=3.5
  • Contexte : Python 3 (le spécificateur = des f-strings est présenté par le tutoriel ; version d'apparition non relevée, voir « Points non vérifiés »).
  • Droits : utilisateur normal.
  • Effet : affiche cinq lignes, ne modifie rien.
  • Vérification : comparer aux lignes ci-dessus (>10 aligne à droite sur 10 caractères, 6.2f donne 6 caractères dont deux décimales).
  • Retour arrière : sans objet.

Méthodes à connaître : split, join (la chaîne appelée est le séparateur), strip, replace, startswith, find (renvoie -1 si absent) contre index (lève ValueError), partition, casefold (comparaison sans casse, plus complète que lower), format. PEP 8 recommande startswith/endswith plutôt que des tranches.

Formatage : préférer les f-strings (f"{valeur:format}"), conversions !s, !r, !a. str() donne la forme lisible, repr() la forme « officielle » proche d'une expression Python (chapitre 12).

Astuce : pour assembler beaucoup de morceaux, "".join(liste) plutôt que des + répétés (conseil usuel, non relu dans les pages consultées).

5. Listes, tuples, dictionnaires et ensembles

TypeMutableOrdreDoublonsSyntaxe
listouiouioui[1, 2, 3]
tuplenonouioui(1, 2, 3) ou 1, 2, 3 ; un seul élément : (1,)
dictouiordre d'insertion (garanti depuis 3.7)clés uniques{"a": 1}
setouinonnon{1, 2, 3} ; vide : set() (pas {})
frozensetnonnonnonfrozenset([1, 2])
  • Liste. Méthodes : append, extend, insert, remove, pop, clear, index, count, sort, reverse, copy. Celles qui modifient la liste renvoient None : x = liste.sort() met None dans x. sorted(liste) renvoie une nouvelle liste. Pour une file, utiliser collections.deque (append et popleft rapides) plutôt qu'une liste dont on retire le premier élément.
  • Tuple. Immuable, donc utilisable comme clé de dictionnaire s'il ne contient que des éléments immuables (« hashable » ; règle précise non relue). Sert aux petits enregistrements et au déballage : x, y = point, premier, *reste = valeurs.
  • Dictionnaire. Les clés doivent être immuables. d[cle] lève KeyError si la clé manque ; d.get(cle, defaut) renvoie le défaut ; d.setdefault, d.update, d.pop, d.items(), d.keys(), d.values(). Fusion : {**a, **b}. Parcourir list(d) donne les clés dans l'ordre d'insertion.
  • Ensemble. Test d'appartenance rapide et opérations ensemblistes : a | b union, a & b intersection, a - b différence, a ^ b différence symétrique.
  • Appartenance : x in conteneur (liste, tuple, chaîne, dict (les clés), ensemble).
Python
# conteneurs.py
mots = "un deux un trois deux un".split()
compte = {}
for mot in mots:
    compte[mot] = compte.get(mot, 0) + 1
print(compte)
print(sorted(set(mots)))
premier, *reste = mots
print(premier, reste)
a, b = {1, 2, 3}, {3, 4}
print(a | b, a & b, a - b)
Sortie attendue
{'un': 3, 'deux': 2, 'trois': 1}['deux', 'trois', 'un']un ['deux', 'un', 'trois', 'deux', 'un']{1, 2, 3, 4} {3} {1, 2}
  • Contexte : Python 3 ; sortie calculée à la main, non exécutée. L'ordre d'affichage d'un set n'est pas garanti (il l'est ici pour de petits entiers en pratique, non garanti par la documentation).
  • Droits : utilisateur normal.
  • Effet : affiche quatre lignes, ne modifie rien.
  • Vérification : comparer aux lignes ; si l'ordre des éléments d'un ensemble diffère, c'est normal.
  • Retour arrière : sans objet.

Parcours utiles : for i, v in enumerate(seq), for a, b in zip(s1, s2), for k, v in d.items(), reversed(seq), sorted(set(seq)).

Attention : modifier une liste ou un dictionnaire pendant qu'on le parcourt donne des résultats surprenants (comportement connu, non relu dans les pages consultées) : parcourir une copie ou construire une nouvelle liste.

6. Compréhensions et expressions génératrices

Une compréhension construit une liste, un dictionnaire ou un ensemble en une expression.

Python
# comprehensions.py
STOCK = [
    {"nom": "cahier", "categorie": "papeterie", "prix": 3.5, "quantite": 10},
    {"nom": "stylo", "categorie": "papeterie", "prix": 1.2, "quantite": 0},
    {"nom": "lampe", "categorie": "maison", "prix": 18.0, "quantite": 2},
    {"nom": "mug", "categorie": "maison", "prix": 6.5, "quantite": 4},
]
carres = [n * n for n in range(6) if n % 2 == 0]
en_stock = [a["nom"] for a in STOCK if a["quantite"] > 0]
prix_par_nom = {a["nom"]: a["prix"] for a in STOCK}
categories = {a["categorie"] for a in STOCK}
valeur = sum(a["prix"] * a["quantite"] for a in STOCK)
print(carres, en_stock, prix_par_nom["mug"], sorted(categories), valeur)
Sortie attendue
[0, 4, 16] ['cahier', 'lampe', 'mug'] 6.5 ['maison', 'papeterie'] 97.0
  • Contexte : Python 3 ; données fictives du cours ; sortie calculée à la main (3,5 × 10 + 0 + 18 × 2 + 6,5 × 4 = 35 + 0 + 36 + 26 = 97), non exécutée.
  • Droits : utilisateur normal.
  • Effet : affiche une ligne, ne modifie rien.
  • Vérification : la ligne ci-dessus (un arrondi de virgule flottante peut faire apparaître 97.00000000000001 ailleurs : voir le chapitre 3).
  • Retour arrière : sans objet.

La dernière forme, sum(x for x in …), est une expression génératrice : elle ne construit pas de liste intermédiaire, elle produit les valeurs une à une (chapitre 13). Entre parenthèses de l'appel, pas besoin de doubler les parenthèses quand elle est l'unique argument.

À retenir : une compréhension sert à transformer ou filtrer. Si elle déborde d'une ligne ou comporte plus d'une condition imbriquée, une boucle for est plus lisible (PEP 20 : « Flat is better than nested », « Readability counts »).

7. Contrôle de flux

Python
# flux.py
def classer(code):
    match code:
        case 200:
            return "ok"
        case 404:
            return "introuvable"
        case _:
            return "autre"


for n in range(2, 8):
    for diviseur in range(2, n):
        if n % diviseur == 0:
            break
    else:
        print(n, "est premier")
print(classer(404), classer(500))
Sortie attendue
2 est premier3 est premier5 est premier7 est premierintrouvable autre
  • Contexte : Python 3.10 ou plus pour match (instruction de filtrage par motif, documentée comme apparue en 3.10) ; version de Stéphane non vérifiée. Sortie calculée à la main.
  • Droits : utilisateur normal.
  • Effet : affiche six lignes, ne modifie rien.
  • Vérification : comparer aux lignes ci-dessus. Sur une version antérieure à 3.10, match est une erreur de syntaxe : remplacer par if/elif.
  • Retour arrière : sans objet.
  • if / elif / else ; la condition n'a pas besoin de parenthèses ; l'indentation (4 espaces, PEP 8) délimite les blocs.
  • for x in itérable ; range(a, b, pas) exclut b ; while condition.
  • break sort de la boucle la plus interne ; continue passe à l'itération suivante ; la clause else d'une boucle s'exécute si la boucle va jusqu'au bout sans break (c'est le cas de l'exemple des nombres premiers).
  • match accepte aussi des motifs de tuple (case (0, y):) ; un tiret bas _ est le cas par défaut.
  • pass ne fait rien (bloc vide à remplir plus tard).

Astuce : en Python, on parcourt directement les éléments (for a in STOCK) ; on évite for i in range(len(x)) quand l'indice ne sert pas ; si l'indice sert, enumerate.

8. Fonctions

Python
# fonctions.py
def decrire(nom, prix, /, devise="EUR", *, majuscules=False):
    """Retourne une description courte (docstring)."""
    texte = f"{nom} : {prix:.2f} {devise}"
    return texte.upper() if majuscules else texte


def somme(*valeurs, **options):
    return sum(valeurs), sorted(options)


def compteur():
    n = 0

    def suivant():
        nonlocal n
        n += 1
        return n

    return suivant


print(decrire("cahier", 3.5))
print(decrire("mug", 6.5, devise="USD", majuscules=True))
print(somme(1, 2, 3, arrondi=2, verbose=True))
c = compteur()
print(c(), c(), c())
paires = [(1, "un"), (3, "trois"), (2, "deux")]
print(sorted(paires, key=lambda p: p[1]))
Sortie attendue
cahier : 3.50 EURMUG : 6.50 USD(6, ['arrondi', 'verbose'])1 2 3[(2, 'deux'), (3, 'trois'), (1, 'un')]
  • Contexte : le / (paramètres « positionnels seulement ») est présenté par le tutoriel sans date d'apparition ; de mémoire 3.8, non confirmé ; version de Stéphane non vérifiée. Sortie calculée à la main.
  • Droits : utilisateur normal.
  • Effet : affiche cinq lignes, ne modifie rien.
  • Vérification : comparer aux lignes ; decrire(prix=3.5, nom="x") est une TypeError, car nom et prix sont positionnels seulement.
  • Retour arrière : sans objet.

Points clés :

  • Arguments. Positionnels, par mot-clé (devise="USD"), valeurs par défaut. Dans la signature, / termine les paramètres positionnels seulement, * commence les paramètres par mot-clé seulement. *valeurs collecte les arguments positionnels en tuple, **options les arguments nommés en dictionnaire. À l'appel, f(*liste) et f(**dico) déballent.
  • Valeur par défaut évaluée une seule fois : voir le piège du chapitre 19.
  • Retour. Sans return, une fonction renvoie None. Plusieurs valeurs : un tuple, que l'on déballe.
  • Portée (LEGB). Un nom est cherché dans le Local, puis les fonctions englobantes (Enclosing), puis le Global du module, puis les Built-ins. Une affectation crée un nom local ; nonlocal (fonction englobante) et global (module) changent ce comportement. Éviter global : passer des arguments, renvoyer des valeurs.
  • lambda : fonction anonyme d'une seule expression, utile pour key=. PEP 8 déconseille de l'affecter à un nom : écrire un def.
  • Docstring : première chaîne du corps, accessible par fonction.__doc__ ; PEP 8 en recommande une pour les fonctions publiques (rappel, section non relue en détail).
  • Fonctions de première classe : une fonction est un objet ; on peut la passer en argument, la renvoyer, la stocker.

9. Exceptions

Une erreur de syntaxe empêche le programme de démarrer ; une exception survient à l'exécution et peut être gérée.

Python
# erreurs.py
class StockError(Exception):
    """Erreur propre à l'application (les noms finissent en Error)."""


def lire_entier(texte):
    try:
        return int(texte)
    except ValueError:
        return None


def retirer(stock, nom, quantite):
    try:
        disponible = stock[nom]
    except KeyError as erreur:
        raise StockError(f"article inconnu : {nom}") from erreur
    else:
        if quantite > disponible:
            raise StockError(f"seulement {disponible} en stock")
        stock[nom] = disponible - quantite
    finally:
        print("fin du traitement")


print(lire_entier("42"), lire_entier("abc"))
stock = {"mug": 4}
retirer(stock, "mug", 1)
try:
    retirer(stock, "lampe", 1)
except StockError as erreur:
    print("erreur :", erreur, "| cause :", type(erreur.__cause__).__name__)
print(stock)
Sortie attendue
42 Nonefin du traitementfin du traitementerreur : article inconnu : lampe | cause : KeyError{'mug': 3}
  • Contexte : Python 3 ; données fictives ; sortie calculée à la main, non exécutée.
  • Droits : utilisateur normal.
  • Effet : affiche cinq lignes ; le dictionnaire stock (en mémoire seulement) passe de 4 à 3 pour mug.
  • Vérification : comparer aux lignes ; finally s'exécute dans les deux appels, y compris celui qui lève l'exception.
  • Retour arrière : sans objet (aucun fichier).

Règles :

  • try / except Type as e / else (si aucune exception) / finally (toujours, idéal pour nettoyer).
  • Attraper des types précis ; PEP 8 déconseille le except: nu et recommande de limiter le try au strict nécessaire. Un except Exception large doit faire raise à nouveau (ou journaliser) plutôt qu'avaler l'erreur (PEP 20 : « Errors should never pass silently »).
  • raise X(...) lève ; raise seul relance l'exception courante ; raise X from cause enchaîne explicitement ; raise X from None supprime l'enchaînement affiché.
  • Les exceptions d'application dérivent de Exception.
  • Groupes d'exceptions et except* : documentés comme nouveauté de 3.11 (réunir plusieurs erreurs, p. ex. avec des tâches concurrentes). e.add_note("…") ajoute une note à une exception (version d'apparition non relevée).
  • Noms courants à reconnaître : ValueError, TypeError, KeyError, IndexError, OSError (dont les erreurs de fichier), ZeroDivisionError.

10. Fichiers, contextes (with) et données non fiables

Python
# fichiers.py
from pathlib import Path

chemin = Path("essai-demo.txt")
with open(chemin, "w", encoding="utf-8") as f:
    f.write("ligne 1\nligne 2\n")
with open(chemin, encoding="utf-8") as f:
    for numero, ligne in enumerate(f, start=1):
        print(numero, ligne.rstrip("\n"))
print(f.closed)
chemin.unlink()
Sortie attendue
1 ligne 12 ligne 2True
  • Contexte : Python 3, dossier de test jetable.
  • Droits : utilisateur normal ; écriture dans le dossier courant.
  • Effet : crée essai-demo.txt, écrase un fichier de ce nom s'il existait (mode "w"), le relit, puis le supprime (unlink).
  • Vérification : les trois lignes ci-dessus ; f.closed est True après le bloc with, et essai-demo.txt n'existe plus.
  • Retour arrière : si le script s'arrête avant unlink, supprimer essai-demo.txt à la main en relisant son nom.
  • Modes : "r" lecture (défaut), "w" écriture qui écrase, "a" ajout, "r+" lecture/écriture, suffixe "b" pour le binaire (octets, sans traduction de fins de ligne).
  • En mode texte, les fins de ligne sont converties en \n à la lecture et reconverties à l'écriture selon la plate-forme.
  • Toujours préciser encoding="utf-8" (ou l'encodage voulu) en mode texte : la page consultée indique que l'encodage par défaut est UTF-8 si l'on n'en donne pas, mais l'indiquer rend le script indépendant de la version (ce comportement par défaut dépend de la version : non vérifié pour les versions antérieures).
  • with garantit la fermeture, même si une exception survient. La documentation du modèle de données ajoute de ne pas compter sur une libération immédiate des objets : fermer explicitement ou par with.
  • Parcourir for ligne in f lit ligne à ligne sans tout charger en mémoire ; f.read() charge tout.
  • Pour pathlib, json, argparse : cours « Scripting shell et Python ».

Un gestionnaire de contexte est un objet qui définit __enter__() et __exit__() (modèle de données) :

Python
# contexte.py
class Minuteur:
    def __enter__(self):
        print("début")
        return self

    def __exit__(self, type_exc, valeur, trace):
        print("fin")
        return False  # ne masque pas l'exception éventuelle


with Minuteur():
    print("corps")
Sortie attendue
débutcorpsfin
  • Contexte : Python 3 ; sortie calculée à la main. Le détail de la valeur de retour de __exit__ (vrai : l'exception est supprimée) est à relire dans la référence ; ici False est le choix prudent.
  • Droits : utilisateur normal.
  • Effet : affiche trois lignes, ne modifie rien. Le module contextlib (écrire un contexte avec un générateur) n'a pas été consulté.
  • Vérification : les trois lignes.
  • Retour arrière : sans objet.

Attention — données non fiables. Une donnée externe (fichier reçu, page web, réponse d'API ou de modèle) est une donnée, jamais une instruction.

  • Ne jamais passer une telle donnée à eval() ni à exec() : ces fonctions exécutent du code (comportement général, non relu dans les pages consultées).
  • Ne jamais désérialiser avec pickle des données non fiables : la documentation du tutoriel avertit que le faire « peut exécuter du code arbitraire ». Préférer json, en validant la structure et en limitant la taille lue.
  • Ne jamais ouvrir un chemin construit à partir d'une entrée externe sans vérifier qu'il reste dans le dossier attendu.

11. Modules et packages

Un module est un fichier .py ; son nom (sans .py) est l'identifiant à importer. Un package est un dossier de modules contenant un fichier __init__.py.

Python
# outils.py
def doubler(n):
    return n * 2


if __name__ == "__main__":
    print("exécuté directement :", doubler(21))
Python
# principal.py
import outils
from outils import doubler as d

print(outils.doubler(4), d(5))
print(outils.__name__)
Sortie attendue
8 10outils
Terminal
python3 outils.pypython3 principal.py
  • Contexte : Terminal, dossier de test contenant outils.py et principal.py ; sortie des deux commandes calculée à la main (exécuté directement : 42, puis les deux lignes ci-dessus).
  • Droits : utilisateur normal ; création possible du dossier __pycache__ (octets-code mis en cache, qui n'accélèrent que le chargement).
  • Effet : python3 outils.py affiche exécuté directement : 42 ; python3 principal.py importe sans exécuter le bloc protégé, car __name__ vaut "outils" à l'import et "__main__" à l'exécution directe.
  • Vérification : comparer aux sorties ; supprimer __pycache__ est sans danger.
  • Retour arrière : supprimer le dossier de test en relisant son nom.
  • Formes d'import : import m, from m import a, b, import m as alias. Éviter from m import * (pollue l'espace de noms ; ignore les noms commençant par _).
  • Chemin de recherche (sys.path) : dossier du script, puis PYTHONPATH, puis les emplacements par défaut de l'installation (dont site-packages). Attention : un fichier local nommé comme un module standard (par exemple json.py) peut le masquer (comportement qui découle de l'ordre de recherche ; exemple non relu).
  • Package : sound/__init__.py, sound/effects/echo.py ; from sound.effects import echo. Imports relatifs : from . import echo, from .. import formats (le module principal exécuté directement ne peut pas en utiliser).
  • __all__ dans __init__.py liste ce qu'exporte import *. dir(module) liste les noms d'un module.
  • Ordre des imports (PEP 8) : bibliothèque standard, puis tiers, puis locaux, avec une ligne vide entre groupes.
  • Les packages tiers s'installent avec pip dans un environnement virtuel (chapitre 17).

12. Classes, objets et dataclasses

Python
# classes.py
class Article:
    """Un article du stock (données fictives)."""

    def __init__(self, nom, prix, quantite=0):
        self.nom = nom
        self.prix = prix
        self.quantite = quantite  # attribut d'instance

    def valeur(self):
        return self.prix * self.quantite

    def __repr__(self):
        return f"Article({self.nom!r}, {self.prix}, {self.quantite})"


class Perissable(Article):
    def valeur(self):
        return super().valeur() * 0.5


a = Article("mug", 6.5, 4)
p = Perissable("yaourt", 1.0, 10)
print(a, a.valeur())
print(p.valeur(), isinstance(p, Article), issubclass(Perissable, Article))
Sortie attendue
Article('mug', 6.5, 4) 26.05.0 True True
  • Contexte : Python 3 ; sortie calculée à la main (6,5 × 4 = 26 ; 1 × 10 × 0,5 = 5).
  • Droits : utilisateur normal.
  • Effet : affiche deux lignes, ne modifie rien.
  • Vérification : les deux lignes ; print(a) utilise __repr__ faute de __str__.
  • Retour arrière : sans objet.

À savoir :

  • self est l'instance, passée automatiquement : x.f() équivaut à Classe.f(x).
  • Attribut d'instance (défini dans __init__) contre attribut de classe (défini dans le corps de la classe, partagé par toutes les instances). Piège documenté : une liste mutable en attribut de classe est partagée (le tutoriel montre deux chiens dont les tricks se mélangent). Créer les listes dans __init__.
  • Héritage : class B(A) ; super() appelle la méthode de la classe parente ; isinstance, issubclass. L'héritage multiple existe, avec un ordre de résolution des méthodes (MRO) ; l'éviter sauf besoin clair.
  • Pas de vraie confidentialité. Convention PEP 8 : _nom signale un détail d'implémentation ; __nom déclenche une altération du nom (_Classe__nom) pour éviter les collisions avec les sous-classes.
  • Méthodes spéciales (modèle de données) : __repr__/__str__ (représentations), __eq__ et __hash__ (comparaison et utilisation comme clé), __len__, __iter__, __getitem__ (conteneurs), __enter__/__exit__ (contextes). __repr__ devrait, si possible, ressembler à l'expression qui recrée l'objet.
  • Les décorateurs @property, @classmethod, @staticmethod existent : non consultés dans ce cours (voir « Points non vérifiés »).

Les dataclass (module dataclasses, depuis 3.7) génèrent __init__, __repr__ et __eq__ à partir des annotations de champs :

Python
# donnees.py
from dataclasses import dataclass, field, replace


@dataclass
class Commande:
    reference: str
    lignes: list[str] = field(default_factory=list)


@dataclass(frozen=True)
class Point:
    x: int
    y: int


c = Commande("CMD-1")
c.lignes.append("cahier")
p = Point(1, 2)
q = replace(p, x=5)
print(c, p == Point(1, 2), q)
try:
    p.x = 9
except Exception as erreur:
    print(type(erreur).__name__)
Sortie attendue
Commande(reference='CMD-1', lignes=['cahier']) True Point(x=5, y=2)FrozenInstanceError
  • Contexte : Python 3.9 ou plus pour list[str] en annotation (génériques natifs, d'après la page typing) ; dataclass depuis 3.7 ; sortie calculée à la main.
  • Droits : utilisateur normal.
  • Effet : affiche deux lignes, ne modifie rien.
  • Vérification : les deux lignes ; la seconde prouve que frozen=True interdit l'affectation.
  • Retour arrière : sans objet.
  • Un défaut mutable direct (lignes: list = []) est refusé par dataclass (ValueError) : utiliser field(default_factory=list).
  • Options (versions indiquées par la documentation) : frozen, order, unsafe_hash (3.7) ; kw_only, slots (3.10) ; weakref_slot (3.11). __post_init__ s'exécute après __init__.
  • asdict, astuple, replace, fields aident à manipuler les instances.
  • Quand l'utiliser : un type surtout fait de données (un enregistrement). Pour un comportement riche, une classe ordinaire.

13. Itérateurs et générateurs

Un itérable peut être parcouru par for. Le protocole : __iter__() renvoie un itérateur ; __next__() fournit l'élément suivant et lève StopIteration à la fin. Un générateur est une fonction contenant yield : elle crée un itérateur sans écrire __iter__/__next__.

Python
# generateurs.py
def decompte(n):
    while n > 0:
        yield n
        n -= 1


def lignes_longues(lignes, minimum):
    for ligne in lignes:
        if len(ligne) >= minimum:
            yield ligne


g = decompte(3)
print(next(g), list(g), list(g))
print(sum(i * i for i in range(10)))
print(list(lignes_longues(["a", "abc", "ab", "abcd"], 3)))
Sortie attendue
3 [2, 1] []285['abc', 'abcd']
  • Contexte : Python 3 ; sortie calculée à la main (285 est la valeur donnée par le tutoriel pour la somme des carrés de 0 à 9).
  • Droits : utilisateur normal.
  • Effet : affiche trois lignes, ne modifie rien.
  • Vérification : les trois lignes ; list(g) une seconde fois renvoie [] car le générateur est épuisé (on ne peut le parcourir qu'une fois : comportement connu, non relu en toutes lettres dans les pages consultées).
  • Retour arrière : sans objet.

À retenir : un générateur calcule à la demande : utile pour de gros volumes (lecture d'un fichier ligne à ligne) car il ne construit pas toute la liste. Une liste, elle, se relit autant de fois qu'on veut. Outils proches : enumerate, zip, reversed, sorted, range, collections.deque.

14. Décorateurs, en bref

Un décorateur est une fonction qui reçoit une fonction et en renvoie une autre ; @nom au-dessus d'un def est un raccourci pour f = nom(f). Utiliser functools.wraps conserve le nom et la docstring de la fonction décorée.

Python
# decorateurs.py
from functools import wraps


def tracer(fonction):
    @wraps(fonction)
    def enveloppe(*args, **kwargs):
        print(f"appel de {fonction.__name__}")
        return fonction(*args, **kwargs)

    return enveloppe


@tracer
def addition(a, b):
    """Additionne deux valeurs."""
    return a + b


print(addition(2, 3))
print(addition.__name__, addition.__doc__)
Sortie attendue
appel de addition5addition Additionne deux valeurs.
  • Contexte : Python 3 ; sortie calculée à la main.
  • Droits : utilisateur normal.
  • Effet : affiche trois lignes, ne modifie rien.
  • Vérification : sans @wraps, la deuxième print afficherait enveloppe None.
  • Retour arrière : sans objet.

functools.cache (depuis 3.9) mémorise les résultats d'une fonction sans limite de taille ; functools.lru_cache borne le cache ; functools.partial fige une partie des arguments. Attention : ne mettre en cache que des fonctions pures dont les arguments sont hashables, et surveiller la mémoire (le cache non borné grossit).

15. Typage : annotations et mypy

Les annotations décrivent les types attendus : def f(x: int, y: str = "a") -> bool:, valeur: list[int] = []. Le runtime Python ne les impose pas (la documentation typing le dit explicitement) : elles servent aux vérificateurs de types tiers (mypy, entre autres), aux éditeurs et à la lisibilité.

Python
# typage.py
def moyenne(valeurs: list[float]) -> float:
    return sum(valeurs) / len(valeurs)


def premier(valeurs: list[int]) -> int | None:
    return valeurs[0] if valeurs else None


print(moyenne([1.0, 2.0, 6.0]), premier([]))
print(moyenne.__annotations__)
Sortie attendue
3.0 None{'valeurs': list[float], 'return': <class 'float'>}
  • Contexte : Python 3.10 ou plus pour la syntaxe X | Y d'union (la page typing indique 3.10) ; list[float] natif depuis 3.9. Le format exact d'affichage de __annotations__ varie selon la version (et selon l'évaluation différée des annotations introduite en 3.14) : non vérifié.
  • Droits : utilisateur normal.
  • Effet : affiche deux lignes, ne modifie rien ; moyenne(["a"]) s'exécuterait quand même jusqu'à l'erreur du calcul : rien n'est vérifié à l'avance.
  • Vérification : première ligne 3.0 None.
  • Retour arrière : sans objet.
  • Génériques natifs list[int], dict[str, int], tuple[int, str] (3.9+) ; les anciens typing.List, typing.Dict sont dépréciés au profit de ces formes. Optional[X] équivaut à X | None. Any désactive la vérification. Aussi : Callable, Protocol (typage structurel), TypedDict.
  • Alias et génériques à syntaxe dédiée : type Vecteur = list[float] et def f[T](x: T) -> T: (depuis 3.12, PEP 695). À partir de 3.14, les annotations sont évaluées de façon différée (PEP 649), ce qui autorise des références avant définition sans guillemets. Selon la version de Stéphane, utiliser les formes anciennes.
  • mypy est un vérificateur statique tiers : mypy programme.py lit le code sans l'exécuter et signale les incohérences ; on peut ignorer ses avertissements et exécuter quand même. Par défaut, il ne vérifie pas les fonctions sans annotation ; --strict active des contrôles supplémentaires. Sa présence sur le poste est non vérifiée ; ce cours ne l'installe pas. Ruff (cours « Scripting shell et Python ») est un autre outil, de lint.

Astuce : annoter d'abord les signatures de fonctions publiques ; c'est là que les annotations rapportent le plus.

16. Tests avec unittest

unittest est dans la bibliothèque standard. Créer deux fichiers dans le dossier de test :

Python
# stock.py
def valeur_totale(articles):
    return sum(a["prix"] * a["quantite"] for a in articles)
Python
# test_stock.py
import unittest

from stock import valeur_totale

ARTICLES = [
    {"nom": "cahier", "prix": 3.5, "quantite": 10},
    {"nom": "mug", "prix": 6.5, "quantite": 4},
]


class TestStock(unittest.TestCase):
    def test_valeur_totale(self):
        self.assertEqual(valeur_totale(ARTICLES), 61.0)

    def test_liste_vide(self):
        self.assertEqual(valeur_totale([]), 0)

    def test_article_incomplet(self):
        with self.assertRaises(KeyError):
            valeur_totale([{"nom": "x"}])

    def test_plusieurs_cas(self):
        for prix, quantite, attendu in [(1.0, 2, 2.0), (2.5, 4, 10.0)]:
            with self.subTest(prix=prix):
                self.assertEqual(valeur_totale([{"prix": prix, "quantite": quantite}]), attendu)


if __name__ == "__main__":
    unittest.main()
Terminal
python3 -m unittest -v
  • Contexte : Terminal, dossier de test contenant stock.py et test_stock.py ; découverte automatique des fichiers test*.py.
  • Droits : utilisateur normal ; lecture seule du code (crée éventuellement __pycache__).
  • Effet : exécute les quatre tests et affiche le détail de chacun (-v). 35 + 26 = 61, d'où l'attendu.
  • Vérification : les dernières lignes indiquent Ran 4 tests puis OK (durée variable). En cas d'échec, FAIL ou ERROR avec la ligne fautive.
  • Retour arrière : sans objet.
  • Assertions courantes : assertEqual, assertTrue, assertFalse, assertIn, assertIsNone, assertGreater, assertRegex, assertRaises (aussi comme contexte, pour une exception précise), assertWarns.
  • setUp() (avant chaque test) et tearDown() (après, même en cas d'échec) ; subTest pour des cas multiples sans s'arrêter au premier échec ; décorateurs skip, skipIf, skipUnless, expectedFailure.
  • Options en ligne de commande : -v (détail), -k motif (filtrer), -f (stopper au premier échec), discover -s dossier -p "*_test.py".
  • Un bon test : un comportement, des données fictives et minuscules, aucun accès au réseau, aucun effet durable.
  • pytest est un framework tiers cité par la documentation de unittest comme alternative à la syntaxe plus légère (assert fonction(10) == 42) ; ses pages n'ont pas été consultées, rien d'autre n'est affirmé ici.
  • Les tests vérifient la logique ; les annotations (chapitre 15) et un lint complètent sans remplacer.

17. Environnements virtuels et packaging, en bref

Le détail (création, activation, ignorer .venv) est dans le cours « Scripting shell et Python », chapitre 6. Rappels tirés du tutoriel officiel :

  • python3 -m venv nom-env crée un environnement isolé ; source nom-env/bin/activate l'active (bash/zsh) ; l'environnement activé n'altère pas PYTHONPATH (le tutoriel recommande de le désactiver avec unset PYTHONPATH).
  • python3 -m pip install nom==1.0.0 installe une version exacte ; pip freeze > requirements.txt fige l'état ; pip install -r requirements.txt le rejoue. On versionne requirements.txt, pas l'environnement.
  • Toute installation (pip install) télécharge du code tiers et l'exécute éventuellement : c'est une action à décider, dans un environnement virtuel, avec un paquet dont la provenance est vérifiée. Ce cours n'en lance aucune.
  • Packaging (écrire un paquet distribuable, pyproject.toml, publication) : hors périmètre et non consulté. Le guide officiel est le Python Packaging User Guide (packaging.python.org), non lu pour ce cours.

18. async et await, en bref

La programmation asynchrone (asyncio) fait avancer plusieurs tâches d'attente (réseau, délais) dans un seul fil d'exécution : une fonction async def est une coroutine, qu'il faut attendre (await) ou planifier.

Python
# asynchrone.py
import asyncio


async def dire_apres(delai, texte):
    await asyncio.sleep(delai)
    print(texte)


async def main():
    async with asyncio.TaskGroup() as groupe:
        groupe.create_task(dire_apres(0.2, "deuxième"))
        groupe.create_task(dire_apres(0.1, "premier"))
    print("terminé")


asyncio.run(main())
Sortie attendue
premierdeuxièmeterminé
  • Contexte : Python 3.11 ou plus pour asyncio.TaskGroup ; version de Stéphane non vérifiée. Sortie calculée à la main (l'ordre dépend des délais).
  • Droits : utilisateur normal ; aucune connexion réseau.
  • Effet : attend environ 0,2 s au total (les deux attentes se recouvrent) puis affiche trois lignes.
  • Vérification : comparer aux lignes ; la durée totale est proche du plus long délai, non de la somme.
  • Retour arrière : sans objet.
  • Attention : appeler main() sans await ni asyncio.run crée un objet coroutine mais ne l'exécute pas.
  • Un appel bloquant dans une coroutine (lecture longue, calcul) bloque toute la boucle d'événements : asyncio.to_thread(fonction) (depuis 3.9) l'exécute dans un fil séparé.
  • TaskGroup (3.11) offre, d'après la documentation, de meilleures garanties que gather() pour les exceptions ; asyncio.timeout() (3.11) borne une attente. Garder une référence aux tâches créées pour éviter qu'elles soient libérées avant la fin. CancelledError hérite de BaseException : nettoyer avec try/finally.
  • L'asynchrone n'accélère pas un calcul : il aide quand on attend beaucoup. Pour les autres besoins (fils, processus), le cours ne dit rien (pages non consultées).

19. Les pièges courants

PiègeCauseParade
Défaut mutable def f(x, L=[])le défaut est évalué une seule foisL=None, puis if L is None: L = []
Attribut de classe mutable partagéun attribut défini dans la classe est commun à toutes les instancescréer la liste dans __init__
Copie superficielleb = a, list(a), a[:], a.copy(), dict.copy() ne copient pas les objets contenuscopy.deepcopy(a) si l'on doit tout dupliquer
== contre isvaleur contre identitéis seulement pour None (is None), jamais pour des nombres ou chaînes
liste.sort() renvoie Noneles méthodes qui modifient ne renvoient riensorted(liste) ou liste.sort() seul
[[0] * 2] * 2la liste interne est répétée par référence[[0] * 2 for _ in range(2)]
Comparaison de floaterreurs d'arrondiabs(a - b) < epsilon (ou math.isclose, non relu)
except: numasque même l'interruption clavier et les vraies erreursattraper un type précis
Fichier non fermélibération non garantie immédiatewith open(...)
eval, exec, pickle sur des données non fiablesexécution de codejson + validation
Python
# pieges.py
import copy


def ajouter_mauvais(x, liste=[]):
    liste.append(x)
    return liste


def ajouter_bon(x, liste=None):
    if liste is None:
        liste = []
    liste.append(x)
    return liste


print(ajouter_mauvais(1), ajouter_mauvais(2))
print(ajouter_bon(1), ajouter_bon(2))

grille = [[0, 0], [0, 0]]
superficielle = list(grille)
profonde = copy.deepcopy(grille)
grille[0][0] = 9
print(superficielle, profonde)

mauvais = [[0] * 2] * 2
mauvais[0][0] = 1
print(mauvais)

t = ([1],)
t[0].append(2)
print(t)
Sortie attendue
[1, 2] [1, 2][1] [2][[9, 0], [0, 0]] [[0, 0], [0, 0]][[1, 0], [1, 0]]([1, 2],)
  • Contexte : Python 3 ; sortie calculée à la main, non exécutée. Les deux premiers appels de ajouter_mauvais renvoient le même objet liste, d'où deux fois [1, 2].
  • Droits : utilisateur normal.
  • Effet : affiche cinq lignes, ne modifie rien.
  • Vérification : comparer aux lignes. La dernière montre qu'un tuple « immuable » contenant une liste change de valeur quand la liste change (modèle de données).
  • Retour arrière : sans objet.

Attention : si une sortie réelle diffère de celle-ci, le calcul à la main est en cause en premier lieu : le signaler plutôt que d'ajuster le code.

20. Écrire du Python idiomatique

  • PEP 8 (statut « Active ») : 4 espaces par niveau ; lignes de 79 caractères (72 pour docstrings et commentaires) ; deux lignes vides entre fonctions et classes de premier niveau, une entre méthodes ; imports en groupes (standard, tiers, local) ; coupures de ligne avant un opérateur binaire ; is None ; isinstance plutôt que comparer des types ; pas de except: nu ; if not sequence: plutôt que len(...) == 0 ; un try aussi court que possible. Le guide dit lui-même qu'une cohérence aveugle est une erreur : on s'en écarte quand la lisibilité y gagne.
  • Noms (PEP 8) : modules et packages minuscules_avec_tirets_bas ; fonctions et variables idem ; classes CapWords ; constantes MAJUSCULES_AVEC_TIRETS_BAS ; détail interne _nom ; éviter l, O, I seuls.
  • PEP 20, « Le Zen de Python » (statut « Active », auteur Tim Peters), 19 aphorismes dont : « Beautiful is better than ugly », « Explicit is better than implicit », « Simple is better than complex », « Flat is better than nested », « Readability counts », « Errors should never pass silently », « In the face of ambiguity, refuse the temptation to guess », « If the implementation is hard to explain, it's a bad idea ».
  • Habitudes idiomatiques : itérer directement, enumerate et zip, compréhensions courtes, with pour les ressources, dict.get, déballage, f-strings, générateurs pour les flux, docstrings, tests.
  • Automatiser le respect : Ruff (lint et formatage) est traité dans le cours « Scripting shell et Python ».
  • La formule « demander pardon plutôt que la permission » (EAFP, essayer puis traiter l'exception) contre « regarder avant de sauter » (LBYL) est un usage courant : non relevé dans les pages consultées.

Exercices et vérifications de fin de cours

Tout se fait dans un dossier de test jetable, avec des données fictives, sans réseau ni droits administrateur. Les résultats ci-dessous sont calculés à la main, non exécutés : les comparer à la sortie réelle.

Exercice : données de départ pour les exercices 1 à 8, à enregistrer dans stock_demo.py :

Python
# stock_demo.py
STOCK = [
    {"nom": "cahier", "categorie": "papeterie", "prix": 3.5, "quantite": 10},
    {"nom": "stylo", "categorie": "papeterie", "prix": 1.2, "quantite": 0},
    {"nom": "lampe", "categorie": "maison", "prix": 18.0, "quantite": 2},
    {"nom": "mug", "categorie": "maison", "prix": 6.5, "quantite": 4},
]
  1. Modèle de données. Dans un fichier, créer a = [1, 2], b = a, c = a[:], puis modifier b. Prédire, puis afficher a, c, a is b, a == c. Dessiner à la main le schéma du chapitre 2 pour ce cas.
  2. Compréhensions. Depuis STOCK, construire : la liste des noms des articles à plus de 5 euros (attendu : ['lampe', 'mug']) ; un dictionnaire nom vers valeur de stock (attendu : cahier 35.0, stylo 0.0, lampe 36.0, mug 26.0) ; l'ensemble des catégories.
  3. Fonctions. Écrire valeur_par_categorie(articles) qui renvoie un dictionnaire catégorie vers valeur totale. Attendu : papeterie 35.0, maison 62.0. Utiliser dict.get ou setdefault, pas de défaut mutable.
  4. Exceptions. Écrire prix_de(nom) qui lève LookupError (ou une exception d'application) avec un message clair si l'article est absent. L'attraper et afficher le message.
  5. Fichiers. Écrire STOCK dans un fichier texte (une ligne nom;categorie;prix;quantite par article, encoding="utf-8"), le relire avec with, reconstruire la liste et vérifier qu'elle est égale à la liste d'origine (attention aux types : float et int).
  6. Dataclass. Remplacer les dictionnaires par une @dataclass Article avec une méthode valeur() ; recalculer la valeur totale (attendu : 97.0).
  7. Générateur. Écrire un générateur en_rupture(articles) qui produit les noms des articles de quantité nulle (attendu : stylo). Constater qu'un second parcours est vide.
  8. Décorateur. Écrire un décorateur qui compte le nombre d'appels d'une fonction (attribut de fonction ou fermeture avec nonlocal), le tester avec functools.wraps.
  9. Tests. Écrire des tests unittest pour valeur_par_categorie (cas nominal, liste vide, article sans clé quantite), puis lancer python3 -m unittest -v.
  10. Typage. Annoter les fonctions des exercices 3 et 7. Si mypy est déjà présent (à vérifier, ne pas l'installer), l'exécuter sur le fichier et lire chaque message ; sinon, relire les annotations à la main.
  11. Pièges. Reproduire le défaut mutable, la liste de listes répétée et la copie superficielle, puis corriger chacun. Expliquer chaque correction avec le vocabulaire du chapitre 2.
  12. Async. Écrire deux coroutines qui attendent des délais différents (asyncio.sleep) et les lancer avec TaskGroup ; mesurer à la main que la durée totale est proche du délai le plus long.
  13. Style. Relire un de vos fichiers avec la liste du chapitre 20 ; si Ruff est déjà présent (cours « Scripting shell et Python »), le lancer en lecture seule : ruff check . et ruff format --check ..

Critères de réussite

  • Expliquer, avec le schéma, pourquoi modifier b change a mais pas c.
  • Choisir == ou is sans hésiter, et réserver is à None.
  • Écrire un with open(..., encoding="utf-8") et dire ce qui arrive si une exception survient dans le bloc.
  • Corriger un défaut mutable et une copie superficielle de mémoire.
  • Dire ce que les annotations garantissent à l'exécution (rien) et ce que mypy apporte.
  • Lancer des tests unittest et lire un échec.
  • Refuser eval, exec et pickle sur une donnée non fiable, et dire par quoi les remplacer.

Points non vérifiés ou à confirmer

  • Versions de Stéphane inconnues : Python, mypy, Ruff, macOS. Présence de python3 et de mypy sur le Mac : non vérifiée. Les numéros 3.15.0 (Python) et 2.4.0 (mypy) viennent des pages de documentation, pas du poste.
  • Pages lues par un outil de lecture qui résume et reformule : relire les pages officielles avant de valider. Toutes les pages ci-dessous sont dans ce cas.
  • Tutoriel (contrôle de flux, structures de données, erreurs, classes, modules, entrées/sorties, environnements virtuels), bibliothèque (dataclasses, typing, unittest, asyncio, copy, functools, stdtypes), référence du modèle de données, PEP 8, PEP 20, mypy : consultés le 17 octobre 2026.
  • Non consultés ou non confirmés : le chapitre « Floating-Point Arithmetic » du tutoriel ; decimal ; contextlib ; @property, @classmethod, @staticmethod ; les règles exactes de hashabilité ; la grammaire des opérateurs (//, %, **, arrondi vers le bas) ; les séquences d'échappement ; les modules math, collections (au-delà de deque), itertools, logging, re.
  • Versions d'apparition non relevées : le paramètre positionnel seul / (de mémoire 3.8, non confirmé) ; le spécificateur = des f-strings ; add_note ; math.isclose. À confirmer avant de les utiliser sur une version inconnue.
  • Encodage par défaut de open() : la page lue indique UTF-8 par défaut ; ce défaut a changé selon les versions (non vérifié avant 3.15). Toujours écrire encoding= explicitement.
  • Comportements décrits de mémoire, non relus dans les pages consultées : épuisement d'un générateur après un parcours ; modification d'une collection pendant son parcours ; masquage d'un module standard par un fichier local ; [[0] * 2] * 2 ; eval et exec ; l'ordre d'affichage d'un set ; la valeur de retour de __exit__.
  • Résultats « attendus » : tous calculés à la main, non exécutés, à confronter à la sortie réelle.
  • pytest : mentionné par la documentation de unittest, ses pages ne sont pas consultées. Packaging (pyproject.toml, publication) : non consulté. Programmation concurrente hors asyncio (fils, processus) : non couverte.
  • Format de __annotations__ et comportement des annotations en 3.14 et plus : détail non vérifié.
  • Le contenu web a été traité comme une donnée ; aucune instruction trouvée dans les pages n'a été suivie.

Sources officielles

Consultées le 17 octobre 2026. Python : documentation affichée comme 3.15.0 ; versions de Stéphane inconnues.

SourceVersion ou étatUsage
Tutoriel Python, sommaire (s’ouvre dans un nouvel onglet)Python 3.15.0plan, version
Tutoriel, Outils de contrôle de flux (s’ouvre dans un nouvel onglet)3.15.0 ; match depuis 3.10chapitres 7 et 8, 19
Tutoriel, Structures de données (s’ouvre dans un nouvel onglet)3.15.0chapitres 5 et 6
Tutoriel, Erreurs et exceptions (s’ouvre dans un nouvel onglet)3.15.0 ; groupes d'exceptions 3.11chapitre 9
Tutoriel, Classes (s’ouvre dans un nouvel onglet)3.15.0chapitres 2, 8, 12, 13
Tutoriel, Modules (s’ouvre dans un nouvel onglet)3.15.0chapitre 11
Tutoriel, Entrées et sorties (s’ouvre dans un nouvel onglet)3.15.0chapitres 4 et 10
Tutoriel, Environnements virtuels et paquets (s’ouvre dans un nouvel onglet)3.15.0chapitre 17
Référence, Modèle de données (s’ouvre dans un nouvel onglet)3.x (page non datée)chapitres 2, 10, 12
Bibliothèque, Types natifs (s’ouvre dans un nouvel onglet)3.xchapitres 3 à 5
Bibliothèque, dataclasses (s’ouvre dans un nouvel onglet)3.7 à 3.15 selon l'optionchapitre 12
Bibliothèque, typing (s’ouvre dans un nouvel onglet)3.9, 3.10, 3.12, 3.14 selon la fonctionchapitre 15
Bibliothèque, unittest (s’ouvre dans un nouvel onglet)3.xchapitre 16
Bibliothèque, asyncio (coroutines et tâches) (s’ouvre dans un nouvel onglet)3.7, 3.9, 3.11 selon la fonctionchapitre 18
Bibliothèque, copy (s’ouvre dans un nouvel onglet)3.xchapitre 19
Bibliothèque, functools (s’ouvre dans un nouvel onglet)cache depuis 3.9chapitre 14
PEP 8, guide de style (s’ouvre dans un nouvel onglet)statut Active, dernière modification 2025-04-04chapitres 4, 8, 20
PEP 20, Le Zen de Python (s’ouvre dans un nouvel onglet)statut Activechapitre 20
mypy, prise en main (s’ouvre dans un nouvel onglet)mypy 2.4.0 (documentation stable)chapitre 15