Qu’est-ce qu’une closure en Python et Javascript ?
Impossible de trouver une explication simple des closures sur le Net. Pourtant c’est un concept qui peut se comprendre en quelques minutes.
En Python
Scope
Le scope d’une variable, aussi appelée sa portée, est la “zone” dans laquelle une variable est accessible.
Par exemple avec :
def sondage(candidat, intention) : marge = 5 return "%s a %s%% d'intention de vote" % (candidat, intention) print marge
On obtient:
UnboundLocalError: local variable 'marge' referenced before assignment
Car marge
est définie et donc accessible dans sondage()
, mais pas en dehors de sondage()
. On dit que le scope de marge
est limité au block de la fonction sondage()
.
Closure
Une closure est l’inclusion dans la définition d’une fonction d’une variable d’un scope supérieur.
Par exemple :
marge = 5 def sondage(candidat, intention): intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30)
Va afficher :
Schwarzenegger a 25% d'intention de vote
C’est une fonctionalité du langage : Python autorise une fonction à utiliser une variable, malgré le fait qu’elle ne soit pas définie à l’intérieur de la fonction.
On appelle cela closure car la référence de cette variable est “piégée” dans la fonction. Si on met à jour la variable, c’est pris en compte dans la fonction :
marge = 5 def sondage(candidat, intention): intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30) marge = 10 print sondage('Schwarzenegger', 30)
Va afficher :
Schwarzenegger a 25% d'intention de vote Schwarzenegger a 20% d'intention de vote
Car le deuxième print
arrive après que marge
ait été changé.
Pour éviter que des closures vous empêchent de choisir le nom de vos variables dans une fonction, si vous redéfinissez la variable de la closure avant de l’utiliser, Python ignore la closure et initialise la variable :
marge = 5 def sondage(candidat, intention): marge=0 intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30) def sondage(candidat, intention, marge=0): intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30)
Ce qui donne :
Schwarzenegger a 30% d'intention de vote Schwarzenegger a 30% d'intention de vote
Ici marge
est définie en tant qu’argument ou comme variable locale. Donc, Python utilise ce qu’il a sous la main, et ne crée pas de closure.
Attention !
Les closures sont en lecture seule. Si vous décidez de les utiliser, vous ne pouvez pas les modifier :
marge = 5 def sondage(candidat, intention): # ici on utilise la variable sans la définir # donc python créé la closure m = marge marge = m + 7 intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30)
Ceci plante avec un message d’erreur pas très explicite:
UnboundLocalError: local variable 'marge' referenced before assignment
Plus vicieusement, ceci non plus ne marche pas:
marge = 5 def sondage(candidat, intention): # marge += 7 veut dire en fait marge = 7 + marge # et comme marge n'est pas définie python créé la closure ! marge += 7 intention -= marge return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30)
Bref, si vous voulez utiliser une closure et l’incrémenter, il faut creer une variable intermédiaire :
marge = 5 def sondage(candidat, intention): m = m + marge # on écrit pas dans marge, donc pas de problème intention -= m return "%s a %s%% d'intention de vote" % (candidat, intention) print sondage('Schwarzenegger', 30)
En Javascript
Les closure en javascript marchent exactement de la même manière qu’en Python :
var marge = 5; function sondage(candidat, intention){ intention -= marge return candidat + " a " + intention + "% d'intention de vote"; } console.log(sondage('Schwarzenegger', 30));
Output:
Schwarzenegger a 25% d'intention de vote
Mais il y a une différence notable !
Non seulement on peut modifier la closure mais en plus la valeur est aussi modifiée à l’extérieur de la fonction.
var marge = 5; function sondage(candidat, intention){ marge += 7; intention -= marge return candidat + " a " + intention + "% d'intention de vote"; } console.log(sondage('Schwarzenegger', 30)); console.log(marge)
Schwarzenegger a 18% d'intention de vote 12
A quoi cela peut-il servir ?
C’est surtout utile quant on définit une fonction dans une fonction. Par exemple quand on créé un décorateur en Python. Ou quand on on passe la variable à un callback, par exemple avec jQuery:
var somme_de_trucs = 0; $.each(liste_de_trucs, function(i, truc){ somme_de_trucs += truc; }); console.log(somme_de_trucs);
En effet $.each
attend un callback en second parametre, et c’est grace à la closure qu’on peut modifier somme_de_trucs
.
No related posts.
À noter que pour modifier une variable à l’extérieur du scope de la fonction, on peut utiliser le mot de clé global qui permet d’avoir le même comportement qu’en Javascript.
Encore moi pour la grammaire (supprime cette partie du commentaire, mais peut-être que c’est cool de laisser la partie ci-dessus.)
Les closure (+s)
on définie (t)
ai (+t) été changé.
ne créé pas de closure. (crée)
@JM: pas exactement.
global
permet de modifier une variable comme si c’était une variable globale. Il y a des effets de bords possibles (ce qui est un des gros défaut de Javascript) car une variable de tel nom peut exister à la racine du fichier.Une solution plus propre est d’utiliser le mot clé
nonlocal
, qui permet de considérer une variable comme étant celle du scope du dessus le plus proche. On évite ainsi de modifier en cascadeMais
nonlocal
n’existe qu’à partir de Python 3 que personne n’utilise en production.Merci pour les corrections :-) Plus un article est propre, plus il est agréable à lire. Surtout n’hésitez pas à faire le grammar nazi en commentaire, ce sera toujours bien acceuilli.
Cet article est complètement faux. Il y a confusion entre variable globale et closure. Une closure n’est pas une variable, c’est une FONCTION. En fait, créer une closure implique d’avoir une fonction qui retourne une fonction (la closure). Voici un exemple simple à retenir :
Il arrive qu’on ait besoin d’écrire une méthode pour laquelle une partie des arguments ne change jamais. On est alors souvent tenté d’utiliser des variables globales pour ces valeurs “fixes”. Le but des closures est justement d’éviter de déclarer des variables globales, tout en obtenant une méthode agréable à utiliser, qui “connait” les valeurs fixes qu’elle doit utiliser.
Cher ami, une variable ne peut être globale que si elle est déclarée avec le mot clé global en Python, ou si elle n’est pas déclarée avec var en javascript. Ce n’est pas le cas dans l’article.
La closure est un espace de mémoire reservé sous certaines conditions. Elle peut se faire dans le cadre de ton exemple – un retour de fonction – mais ce n’est pas le seul cas. On retrouve des closures dans tout un tas de blocs, dans différentes langages.
Cher Sam,
Dans l’esprit « d’apprendre à un homme à pêcher », voici quelques commandes que vous pouvez taper sur votre console Python, je pense que ça vous éclaircira.
Commençons par déclarer une variable :
»»» x = 5
Et maintenant une fonction l’utilisant :
Quand nous faisons appel à f(), elle se « souvient » de x, même si x a été déclaré en-dehors de son scope.
Jusqu’ici, tout va bien… Soyons fous, créons une deuxième fonction !
Youpi, g() se “souvient” aussi de x ! Essayons de changer x.
f() et g() ont pris en compte la nouvelle valeur. Euh… on parlait pas de valeurs « piégées » ? C’est vraiment ça une closure ? Hmm… il n’y a pas d’objet __closure__ dans f, ni dans g…
Que se passe-t-il si on efface la variable x ?
Ok, x n’est plus disponible, mais est-ce que f s’en souvient quand même ?
Ah… bah non en fait. En plus Python nous parle d’un « global name ‘x’ » ? Il cherche une variable globale appelée x ? Je croyais que c’était seulement si je la déclarais “global x” ?!??!
Pouf, pouf… essayons donc comme ça :
On va générer deux nouvelles fonctions, h et m, qui vont piéger des variables.
Cool, j’ai deux fonctions très utiles (hem…) qui utilisent des valeurs que je n’ai pas besoin de leur passer en paramètre. Et f() a bien gardé la valeur 5, alors que j’ai changé la valeur de z entre-temps ! C’est bien, ça m’a l’air plus sécurisé… je ne voudrais pas que le comportement de ma fonction f change au bon vouloir des variables extérieures ! Il paraît que c’est pas bien. Et d’ailleurs même si je supprime la variable z, et mon générateur aussi :
Waou, mes deux fonctions marchent toujours, ce sont des vraies fermetures ! On le voit bien d’ailleurs :
Pour finir : http://fr.wikipedia.org/wiki/Fermeture_%28informatique%29
Effectivement, j’ai tort. Il va falloir que je mette à jour cet article.