XVII. Les closures▲
XVII-1. Introduction▲
Il a été vu dans le chapitre sur les fonctions que celles‑ci peuvent en inclure d’autres…
2.
3.
4.
5.
6.
7.
8.
9.
>>>
def
fct
(
):
... def
xxx
(
):
... print
(
"xxx"
)
... xxx
(
)
... print
(
"ok"
)
>>>
fct
(
)
xxx
ok
Si maintenant, au lieu d’afficher une chaîne en dur, la fonction incluse affichait une variable provenant de la fonction supérieure…
2.
3.
4.
5.
6.
7.
8.
9.
10.
>>>
def
fct
(
):
... v=
123
... def
xxx
(
):
... print
(
v)
... xxx
(
)
... print
(
"ok"
)
>>>
fct
(
)
123
ok
Cela fonctionne encore, car la fonction interne a accès à la variable de la fonction supérieure. Cela se fait parce que dans la fonction du plus haut niveau, Python crée un espace mémoire spécial pour stocker la référence à cette variable « v ».
Cet espace mémoire est appelé « closure ».
XVII-2. L’instruction « nonlocal »▲
Nous venons de voir que la fonction interne avait accès à la variable v située dans l’espace mémoire « closure ». Mais que se passe-t-il si la fonction interne veut modifier ladite variable v… ?
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
>>>
def
fct
(
):
... v=
123
... def
xxx
(
):
... v+=
1
... print
(
v)
... xxx
(
)
... print
(
"ok"
)
...
>>>
fct
(
)
Traceback (
most recent call last):
File "<stdin>"
, line 1
, in
<
module>
File "<stdin>"
, line 6
, in
fct
xxx
(
)
File "<stdin>"
, line 2
, in
xxx
v+=
1
UnboundLocalError
: local variable 'v'
referenced before assignment
On se retrouve avec un souci analogue à celui déjà vu lors de la tentative de modification d’une variable globale par une fonction (cf. (chapitre sur la visibilité des variables)La visibilité).
Toutefois, l’instruction global
ne convient pas, car la variable v n’est pas située dans le scope global.
Pour accéder à la variable v, Python 3 a introduit une instruction spécifique (qui n’existe pas dans Python 2) qui est nonlocal
.
Cette instruction indique que la variable n’est pas locale. Et n’étant pas globale non plus, elle fait alors partie de l’espace mémoire « closure » qui est simplement l’espace mémoire d’une fonction de premier niveau.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
>>>
def
fct
(
):
... v=
123
... def
xxx
(
):
... nonlocal
v
... v+=
1
... print
(
v)
... xxx
(
)
... print
(
"ok"
)
...
>>>
fct
(
)
124
ok
Note : pour obtenir le même résultat avec Python 2, il est alors nécessaire de passer par l’objet fonction en lui mettant la variable en tant qu’attribut (cf. (chapitre sur les fonctions qui sont aussi des objets)Les fonctions sont aussi des objets).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
>>>
# Exemple Python 2 (qui fonctionne aussi sous Python 3)
>>>
def
fct
(
):
... def
xxx
(
):
... fct.v+=
1
... print
(
fct.v)
... fct.v=
123
... xxx
(
)
... print
(
"ok"
)
...
>>>
fct
(
)
124
ok
XVII-3. À quoi cela sert-il ?▲
Cela permet d’attacher un état à la fonction.
Imaginons l’étal d’un marchand. Celui-ci va devoir établir et afficher le prix de ses fruits.
Une première solution pourra être de passer par des classes et créer un objet…
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
>>>
class
etal:
... def
__init__
(
self, fruit):
... self.__fruit=
fruit
... self.__prix=
0
... def
maj
(
self, modif):
... self.__prix+=
modif
... print
(
"Le prix des
%s
est de
%d
€"
%
(
self.__fruit, self.__prix))
...
>>>
orange=
etal
(
"oranges"
)
>>>
pomme=
etal
(
"pommes"
)
>>>
orange.maj
(
5
)
Le prix des oranges est de 5
€
>>>
pomme.maj
(
2
)
Le prix des pommes est de 2
€
>>>
orange.maj
(-
1
)
Le prix des oranges est de 4
€
>>>
pomme.maj
(
4
)
Le prix des pommes est de 6
€
Cela fonctionne, mais reste une solution lourde pour ce besoin si simple. Les closures permettent d’obtenir le même résultat, mais en passant simplement par des fonctions au lieu de classes.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
>>>
def
etal
(
fruit):
... def
maj
(
modif):
... nonlocal
prix
... prix+=
modif
... print
(
"Le prix des
%s
est de
%d
€"
%
(
fruit, prix))
... prix=
0
... return
maj
>>>
orange=
etal
(
"oranges"
)
>>>
pomme=
etal
(
"pommes"
)
>>>
orange
(
5
)
Le prix des oranges est de 5
€
>>>
pomme
(
2
)
Le prix des pommes est de 2
€
>>>
orange
(-
1
)
Le prix des oranges est de 4
€
>>>
pomme
(
4
)
Le prix des pommes est de 6
€
Notez bien que la fonction etal
(
), quand elle est exécutée, retourne l’objet de la fonction interne maj, (pour simplifier, elle retourne juste son code et non le résultat de son exécution). Ainsi, tout appel à la variable orange
(
) utilisée à ce moment-là comme fonction appelée fait en réalité appel au code de la fonction maj
(
). Mais chaque variable orange ou pomme étant différente, chacune d’elles possède sa propre closure incluant sa propre variable prix.
Et pour faire la même chose sous Python 2, on peut alors revenir vers les attributs de l’objet fonction (méthode qui reste bien évidemment elle aussi utilisable sous Python 3)…
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
>>>
# Exemple Python 2 (qui fonctionne aussi sous Python 3)
>>>
def
etal
(
fruit):
... def
maj
(
modif):
... etal.prix+=
modif
... print
(
"Le prix des
%s
est de
%d
€"
%
(
fruit, etal.prix))
... etal.prix=
0
... return
maj
...
>>>
orange=
etal
(
"oranges"
)
>>>
pomme=
etal
(
"pommes"
)
>>>
orange
(
5
)
Le prix des oranges est de 5
€
>>>
pomme
(
2
)
Le prix des pommes est de 2
€
>>>
orange
(-
1
)
Le prix des oranges est de 4
€
>>>
pomme
(
4
)
Le prix des pommes est de 6
€