Qu'est-ce qu'une closure en Python ?

Qu'est-ce qu'une closure en Python ?

Une closure (on parlera de "fermeture" en français) se produit lorsqu'une fonction imbriquée capture et retient une variable définie dans la fonction qui l'englobe.

La closure n'est pas la variable en question, mais la fonction imbriquée elle-même. Elle se souvient des valeurs de la portée (scope) parente.

Pour créer une closure, il faut trois éléments :

  1. Une fonction définie à l'intérieur d'une autre fonction (fonction imbriquée)

  2. La fonction interne fait référence à une variable définie dans la fonction parente

  3. La fonction parente retourne la fonction interne

Prenons un exemple simple : la cuisine de Patrick !

def cuisine_de_patrick(ingredient_secret):
    # 'ingredient_secret' est une variable locale à la fonction externe

    def cuisiner_plat(nom_du_plat):
        # La fonction interne accède à 'ingredient_secret'
        # Elle l'ajoute systématiquement, quel que soit le plat
        print(f"Patrick cuisine {nom_du_plat} avec une pincée de {ingredient_secret} !")

    return cuisiner_plat

# Création de la closure
patrick_epice = cuisine_de_patrick("Piment d'Espelette")

# À ce stade, 'cuisine_de_patrick' a terminé son exécution.
# Mais 'patrick_epice' a conservé l'ingrédient

patrick_epice("une Omelette")
# Affiche : Patrick cuisine une Omelette avec une pincée de Piment d'Espelette !
PYTHON
Un instant

Créez un compte pour exécuter ce code

Inscrivez-vous gratuitement pour modifier et exécuter du code Python directement dans votre navigateur.

Nous voyons bien que même si la fonction cuisine_de_patrick a terminé son travail, patrick_epice a gardé une référence vers ingredient_secret.

Qu'est-ce que nonlocal en Python ?

La closure est capable de lire la variable capturée, mais elle peut aussi la modifier.

Si vous essayez de modifier une variable immuable du scope parent, Python va considérer que vous souhaitez créer une nouvelle variable locale. Cela provoquerait une erreur.

Il faut indiquer spécifiquement que l'on souhaite modifier la variable locale capturée par la closure via le mot-clé nonlocal :

def creer_compteur():
    compte = 0
    def incrementer():
        nonlocal compte
        compte += 1
        return compte
    return incrementer

mon_compteur = creer_compteur()
print(mon_compteur()) # Affiche 1
print(mon_compteur()) # Affiche 2
PYTHON

Pourquoi utiliser les closures ?

Encapsulation

Les closures permettent de masquer des données au sein d'une fonction, un peu comme les variables privées d'une classe.

def accumulateur():
    total = 0
    def ajouter(nombre):
        nonlocal total
        total += nombre
        return total
    return ajouter

# On crée une instance de l'accumulateur
mon_total = accumulateur()

print(mon_total(10))  # Affiche 10
print(mon_total(5))   # Affiche 15 (10 + 5)
print(mon_total(100)) # Affiche 115
PYTHON

La variable total est bien protégée : elle n'est pas accessible depuis le code global, mais elle est bien présente dans la mémoire.

La fabrique de fonctions

Les closures permettent de générer des fonctions dynamiquement, ce qui permet d'éviter la répétition de code.

Prenons un exemple de générateur de balises HTML :

def generateur_balise(balise):
    def emballer(contenu):
        return f"<{balise}>{contenu}</{balise}>"
    return emballer

# On configure des générateurs spécifiques une seule fois
creer_titre = generateur_balise("h1")
creer_paragraphe = generateur_balise("p")

# On utilise ensuite ces fonctions spécialisées
print(creer_titre("Bienvenue sur mon site")) 
# Affiche : <h1>Bienvenue sur mon site</h1>

print(creer_paragraphe("Ceci est une super closure.")) 
# Affiche : <p>Ceci est une super closure.</p>
PYTHON

La base des décorateurs

Les décorateurs Python fonctionnent grâce au principe des closures. Le décorateur enveloppe une fonction et utilise une closure pour exécuter du code avant et/ou après l'appel de celle-ci.

def mon_decorateur(fonction_a_decorer):
    # 'wrapper' est la closure qui capture 'fonction_a_decorer'
    def wrapper():
        print("Avant")
        fonction_a_decorer()
        print("Après")
    return wrapper

@mon_decorateur
def dire_bonjour():
    print("Bonjour !")


dire_bonjour()
# Avant
# Bonjour !
# Après
PYTHON

wrapper retient la fonction dire_bonjour en y ajoutant son propre comportement.

Bravo, tu es prêt à passer à la suite

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.