Sam & Max: Python, Django, Git et du cul » closure http://sametmax.com Deux développeurs en vadrouille qui se sortent les doigts du code Wed, 05 Feb 2014 14:20:37 +0000 en hourly 1 http://wordpress.org/?v=3.3.1 Qu’est-ce qu’une closure en Python et Javascript ? http://sametmax.com/closure-en-python-et-javascript/ http://sametmax.com/closure-en-python-et-javascript/#comments Sun, 06 May 2012 18:19:04 +0000 Sam http://sametmax.com/?p=561 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.

flattr this!

]]>
http://sametmax.com/closure-en-python-et-javascript/feed/ 6