ProgrammationDéveloppeur Python

Comment fonctionnent les closures en Python ? Quelles sont les particularités d'accès aux variables de la fonction externe et comment éviter les pièges liés à la mutabilité des variables ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les closures sont un mécanisme puissant de Python qui permet de créer des fonctions avec des données "mémorisées" du contexte environnant.

Historique de la question

Dans les langages fonctionnels et en Python, les fonctions sont des objets de premier niveau. Une fonction peut retourner une nouvelle fonction qui "ferme" les variables de son environnement (portée externe).

Problème

Les développeurs se mélangent souvent les pinceaux sur la façon dont les variables fermées "vivent" à l'intérieur d'une closure, quand elles sont lues ou écrites, et comment travailler en toute sécurité avec elles (en particulier avec les objets mutables).

Solution

Si la fonction interne utilise des variables de la fonction externe, elles sont automatiquement "mémorisées" — même si la fonction externe a déjà fini son exécution. Il est évident que pour lire une variable, mais si on veut changer sa valeur — il faut utiliser le mot-clé nonlocal. Lorsqu'on travaille avec des objets mutables (listes, dictionnaires), c'est une zone de risque particulière.

Exemple :

def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2

Caractéristiques clés :

  • La fonction imbriquée mémorise les valeurs des variables qui étaient dans la portée au moment de sa création.
  • Pour changer les variables fermées, utilisez nonlocal (sinon, cette opération crée une variable locale).
  • Les références aux objets dans la closure sont conservées même après la sortie de la fonction externe, ce qui permet de mettre en œuvre des fabriques de fonctions, des compteurs lexiques et bien plus encore.

Questions pièges.

Peut-on modifier la valeur d'une variable fermée à l'intérieur d'une fonction sans nonlocal ?

Non. Si on essaie d'assigner une nouvelle valeur sans indiquer nonlocal, Python interprète cela comme la création d'une nouvelle variable locale, et l'ancienne valeur ne sera pas propagée à l'extérieur.

Exemple :

def make_counter(): count = 0 def inner(): count += 1 # Erreur UnboundLocalError ! return count return inner

Peut-on passer des arguments dans le scope externe via une closure ?

Oui, la closure "mémorisera" toutes les variables accessibles dans la portée externe, y compris les attributs de classes, les variables globales, etc. Mais modifier ces variables demande des efforts spéciaux (comme l'utilisation de nonlocal ou global).

Que se passe-t-il avec les objets mutables à l'intérieur d'une closure ?

Si une variable fermée fait référence à un objet mutable, par exemple, une liste, vous pouvez modifier son contenu sans nonlocal, mais si vous essayez de réassigner la variable — nonlocal sera nécessaire.

Exemple :

def make_appender(): result = [] def append(x): result.append(x) # Cela fonctionne ! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]

Erreurs typiques et anti-patterns

  • Essayer de réassigner une variable dans une closure sans nonlocal.
  • Utiliser une closure pour conserver un état mutable, sans réaliser le risque de fuite de mémoire.
  • Code difficile à lire à cause d'un trop grand nombre de variables fermées.

Exemple de la vie

Cas négatif

Un développeur écrit une closure, modifie une variable sans nonlocal — une UnboundLocalError se produit.

Avantages :

  • Prototypage rapide de compteurs, fabriques.

Inconvénients :

  • Comportement imprévisible, bugs en cas d'erreur avec nonlocal.

Cas positif

Utilisation explicite de nonlocal pour un état contrôlé dans une closure.

Avantages :

  • État bien contrôlé, facile à mettre en œuvre pour des compteurs et des fabriques de fonctions.

Inconvénients :

  • Plus difficile à comprendre et à déboguer de grandes chaînes de closures.