IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Python, de zéro


précédentsommairesuivant

XVII. Les closures

XVII-1. Introduction

Il a été vu dans le chapitre sur les fonctions que celles‑ci peuvent en inclure d’autres…

 
Sélectionnez
1.
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…

 
Sélectionnez
1.
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… ?

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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).

 
Sélectionnez
1.
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…

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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)…

 
Sélectionnez
1.
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

précédentsommairesuivant

Copyright © 2022 Svear (svear@free.fr) Permission est accordée de copier, distribuer ou modifier ce document selon les termes de la « Licence de Documentation Libre GNU » (GNU Free Documentation License), version 1.1 ou toute version ultérieure publiée par la Free Software Foundation.