iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Python · Débutant

Python

Apprendre Python de zéro jusqu'au niveau expert : syntaxe, structures de données, POO, générateurs, décorateurs, typage, tests, async et internals du langage.

Python 3.13uvpytestmypyasyncio
01Introduction et installation02Variables et types de base03Conditions et boucles04Fonctions05Listes, tuples, dictionnaires et ensembles06Modules, fichiers et exceptions07Programmation orientée objet08Itérateurs, générateurs, décorateurs et context managers09Typage statique et tests10Concurrence : threads, processus et asyncio11Niveau expert : internals, métaprogrammation et performance
Chapitre 8·50 min

Itérateurs, générateurs, décorateurs et context managers

C'est ici que Python devient vraiment Python. Ces quatre mécanismes sont partout dans les frameworks — FastAPI, pytest, Django, SQLAlchemy — et les comprendre transforme la lecture de leur code source.

Le protocole d'itération

Un itérable possède __iter__() qui renvoie un itérateur. Un itérateur possède __next__() qui renvoie l'élément suivant ou lève StopIteration.

nombres = [1, 2, 3]
it = iter(nombres)
next(it)   # 1
next(it)   # 2
next(it)   # 3
next(it)   # StopIteration

Un for fait exactement ça en boucle. On peut donc rendre n'importe quel objet itérable :

class CompteARebours:
    def __init__(self, depart: int):
        self.depart = depart
 
    def __iter__(self):
        n = self.depart
        while n > 0:
            yield n
            n -= 1
 
for i in CompteARebours(3):
    print(i)   # 3, 2, 1

Le yield dans __iter__ en fait un générateur, beaucoup plus court qu'implémenter __next__ à la main.

Générateurs

Une fonction qui contient yield devient un générateur. Elle s'exécute jusqu'au yield, rend la valeur, se met en pause, et reprend au next() suivant.

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
 
from itertools import islice
list(islice(fibonacci(), 10))   # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Une suite infinie, en mémoire constante. C'est l'intérêt principal : l'évaluation paresseuse.

Expressions génératrices

Remplacez les crochets d'une compréhension par des parenthèses :

total = sum(x ** 2 for x in range(10_000_000))   # pas de liste de 10M éléments

Pipelines de traitement

Enchaîner des générateurs permet de traiter un fichier de 50 Go avec quelques Mo de RAM :

def lire(chemin):
    with open(chemin, encoding="utf-8") as f:
        yield from f
 
def erreurs(lignes):
    return (l for l in lignes if " 500 " in l)
 
def ips(lignes):
    return (l.split()[0] for l in lignes)
 
from collections import Counter
Counter(ips(erreurs(lire("access.log")))).most_common(5)

yield from délègue à un autre itérable.

itertools

from itertools import chain, groupby, batched, pairwise, product, accumulate
 
list(chain([1, 2], [3]))              # [1, 2, 3]
list(batched(range(7), 3))            # [(0, 1, 2), (3, 4, 5), (6,)]  — Python 3.12+
list(pairwise([1, 2, 3]))             # [(1, 2), (2, 3)]
list(accumulate([1, 2, 3, 4]))        # [1, 3, 6, 10]
list(product("ab", [0, 1]))           # [('a', 0), ('a', 1), ('b', 0), ('b', 1)]
 
ventes = sorted(ventes, key=lambda v: v["mois"])
for mois, groupe in groupby(ventes, key=lambda v: v["mois"]):
    print(mois, sum(v["montant"] for v in groupe))

groupby ne regroupe que les éléments consécutifs : triez d'abord.

Décorateurs

Un décorateur est une fonction qui prend une fonction et renvoie une fonction. La syntaxe @ n'est que du sucre :

@decorateur
def f(): ...
 
# équivaut à
f = decorateur(f)

Un décorateur de chronométrage :

import time
from functools import wraps
 
def chrono(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        debut = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            print(f"{func.__name__} : {time.perf_counter() - debut:.3f}s")
    return wrapper
 
@chrono
def calcul_lourd(n):
    return sum(range(n))
 
calcul_lourd(10_000_000)

@wraps recopie le nom et la docstring de la fonction d'origine. Sans lui, toutes vos fonctions décorées s'appellent wrapper dans les tracebacks.

Décorateur avec paramètres

Il faut un niveau d'imbrication supplémentaire : une fonction qui renvoie un décorateur.

def retry(tentatives: int = 3, delai: float = 1.0, exceptions=(Exception,)):
    def decorateur(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(1, tentatives + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if i == tentatives:
                        raise
                    time.sleep(delai * 2 ** (i - 1))
        return wrapper
    return decorateur
 
@retry(tentatives=5, exceptions=(ConnectionError,))
def appeler_api():
    ...

Décorateurs de la bibliothèque standard

from functools import cache, lru_cache
 
@cache
def fib(n: int) -> int:
    return n if n < 2 else fib(n - 1) + fib(n - 2)
 
fib(200)   # instantané grâce à la mémoïsation

@lru_cache(maxsize=128) borne la taille du cache. Attention : les arguments doivent être hashables.

Context managers

Le with appelle __enter__ à l'entrée et __exit__ à la sortie, même en cas d'exception. On l'utilise pour tout ce qui doit être libéré : fichiers, verrous, connexions, transactions.

class Transaction:
    def __init__(self, conn):
        self.conn = conn
 
    def __enter__(self):
        self.conn.execute("BEGIN")
        return self.conn
 
    def __exit__(self, exc_type, exc, tb):
        if exc_type is None:
            self.conn.execute("COMMIT")
        else:
            self.conn.execute("ROLLBACK")
        return False   # ne pas avaler l'exception

La version courte avec contextlib :

from contextlib import contextmanager
 
@contextmanager
def dossier_courant(chemin):
    import os
    ancien = os.getcwd()
    os.chdir(chemin)
    try:
        yield
    finally:
        os.chdir(ancien)
 
with dossier_courant("/tmp"):
    ...

Tout ce qui précède yield correspond à __enter__, ce qui suit à __exit__.

contextlib offre aussi suppress et ExitStack :

from contextlib import suppress, ExitStack
 
with suppress(FileNotFoundError):
    Path("cache.tmp").unlink()
 
with ExitStack() as stack:
    fichiers = [stack.enter_context(open(p)) for p in chemins]

Exercice : un décorateur de cache avec expiration

import time
from functools import wraps
 
def cache_ttl(secondes: float):
    def decorateur(func):
        memo: dict = {}
        @wraps(func)
        def wrapper(*args):
            maintenant = time.monotonic()
            if args in memo:
                valeur, expire = memo[args]
                if maintenant < expire:
                    return valeur
            valeur = func(*args)
            memo[args] = (valeur, maintenant + secondes)
            return valeur
        wrapper.vider = memo.clear
        return wrapper
    return decorateur
 
@cache_ttl(60)
def taux_change(devise: str) -> float:
    ...

Générateurs pour les flux, décorateurs pour les comportements transversaux, context managers pour les ressources. Une fois ces trois outils intégrés, vous lirez le code des grands frameworks Python sans buter à chaque ligne.

Précédent
Programmation orientée objet
Suivant
Typage statique et tests

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main