J'ai vu et utilisé des fonctions imbriquées en Python et elles correspondent à la définition d'une fermeture. Alors pourquoi sont-ils appelés nested functions
au lieu de closures
?
Les fonctions imbriquées ne sont-elles pas des fermetures parce qu'elles ne sont pas utilisées par le monde externe?
MISE À JOUR: Je lisais sur les fermetures et cela m'a fait réfléchir à ce concept en ce qui concerne Python. J'ai cherché et trouvé l'article mentionné par quelqu'un dans un commentaire ci-dessous, mais je ne comprenais pas complètement l'explication de cet article, c'est pourquoi je pose cette question.
Une fermeture se produit lorsqu'une fonction a accès à une variable locale à partir d'une étendue englobante dont l'exécution est terminée.
def make_printer(msg):
def printer():
print msg
return printer
printer = make_printer('Foo!')
printer()
Lorsque make_printer
est appelé, une nouvelle image est placée sur la pile avec le code compilé pour la fonction printer
en tant que constante et la valeur de msg
en tant que locale. Il crée ensuite et retourne la fonction. La fonction printer
faisant référence à la variable msg
, elle est conservée après le retour de la fonction make_printer
.
Donc, si vos fonctions imbriquées ne le font pas
alors ce ne sont pas des fermetures.
Voici un exemple de fonction imbriquée qui n'est pas une fermeture.
def make_printer(msg):
def printer(msg=msg):
print msg
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
Ici, nous lions la valeur à la valeur par défaut d'un paramètre. Cela se produit lorsque la fonction printer
est créée. Par conséquent, aucune référence à la valeur msg
external à printer
ne doit être conservée après le retour de make_printer
. msg
est simplement une variable locale normale de la fonction printer
dans ce contexte.
La question a déjà été répondue paraaronasterling
Cependant, quelqu'un pourrait être intéressé par la façon dont les variables sont stockées sous le capot.
Avant de venir à l'extrait:
Les fermetures sont des fonctions qui héritent des variables de leur environnement. Lorsque vous transmettez une fonction de rappel en tant qu'argument à une autre fonction qui effectuera des E/S, cette fonction de rappel sera invoquée ultérieurement et cette fonction se souviendra - presque magiquement - du contexte dans lequel elle a été déclarée, ainsi que de toutes les variables disponibles dans ce contexte.
Si une fonction n'utilise pas de variables libres, elle ne forme pas de fermeture.
S'il existe un autre niveau interne qui utilise des variables libres - tous les niveaux précédents sauvegardent l'environnement lexical (exemple à la fin)
les attributs de fonction func_closure
dans python <3.X ou __closure__
en python> 3.X enregistrent les variables libres.
Chaque fonction en python a ces attributs de fermeture, mais elle n'enregistre aucun contenu s'il n'y a pas de variables libres.
exemple: d'attributs de fermeture mais pas de contenu car il n'y a pas de variable libre.
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
NB: FREE VARIABLE IS DOIT CREER UNE FERMETURE.
Je vais expliquer en utilisant le même extrait que ci-dessus:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer() #Output: Foo!
Et toutes les fonctions Python ayant un attribut de fermeture, examinons les variables englobantes associées à une fonction de fermeture.
Voici l'attribut func_closure
pour la fonction printer
>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
L'attribut closure
renvoie un tuple d'objets de cellule contenant des détails sur les variables définies dans la portée englobante.
Le premier élément de func_closure qui pourrait être Aucun ou un tuple de cellules contenant des liaisons pour les variables libres de la fonction et qui est en lecture seule.
>>> dir(printer.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__',
'__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>
Ici, dans la sortie ci-dessus, vous pouvez voir cell_contents
, voyons ce qu'il stocke:
>>> printer.func_closure[0].cell_contents
'Foo!'
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>
Ainsi, lorsque nous avons appelé la fonction printer()
, elle accède à la valeur stockée dans le cell_contents
. Voici comment nous avons obtenu la sortie en tant que "Foo!"
Encore une fois, je vais expliquer en utilisant l'extrait ci-dessus avec quelques modifications:
>>> def make_printer(msg):
... def printer():
... pass
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer.func_closure
>>>
Dans l'extrait de code ci-dessus, je n'ai pas écrit de message dans la fonction imprimante, ainsi, il ne crée aucune variable libre. Comme il n'y a pas de variable libre, il n'y aura pas de contenu à l'intérieur de la fermeture. C'est exactement ce que nous voyons ci-dessus.
Maintenant, je vais expliquer un autre extrait pour effacer tout Free Variable
avec Closure
:
>>> def outer(x):
... def intermediate(y):
... free = 'free'
... def inner(z):
... return '%s %s %s %s' % (x, y, free, z)
... return inner
... return intermediate
...
>>> outer('I')('am')('variable')
'I am free variable'
>>>
>>> inter = outer('I')
>>> inter.func_closure
(<cell at 0x10c989130: str object at 0x10c831b98>,)
>>> inter.func_closure[0].cell_contents
'I'
>>> inn = inter('am')
Nous voyons donc qu'une propriété func_closure
est un tuple de fermeture cellules, nous pouvons les référencer et leur contenu explicitement - une cellule a la propriété "cell_contents"
>>> inn.func_closure
(<cell at 0x10c9807c0: str object at 0x10c9b0990>,
<cell at 0x10c980f68: str object at 0x10c9eaf30>,
<cell at 0x10c989130: str object at 0x10c831b98>)
>>> for i in inn.func_closure:
... print i.cell_contents
...
free
am
I
>>>
Ici, quand nous avons appelé inn
, il fera référence à toutes les variables de sauvegarde libre afin que nous obtenions I am free variable
>>> inn('variable')
'I am free variable'
>>>
Python a un support faible pour la fermeture. Pour voir ce que je veux dire, prenons l'exemple suivant d'un compteur utilisant une fermeture avec JavaScript:
function initCounter(){
var x = 0;
function counter () {
x += 1;
console.log(x);
};
return counter;
}
count = initCounter();
count(); //Prints 1
count(); //Prints 2
count(); //Prints 3
La fermeture est assez élégante car elle donne aux fonctions écrites de la sorte la possibilité d'avoir une "mémoire interne". A partir de Python 2.7, ce n'est pas possible. Si tu essayes
def initCounter():
x = 0;
def counter ():
x += 1 ##Error, x not defined
print x
return counter
count = initCounter();
count(); ##Error
count();
count();
Vous obtiendrez une erreur en disant que x n'est pas défini. Mais comment cela se peut-il s’il a été démontré par d’autres que vous pouvez l’imprimer? Cela est dû à la façon dont Python gère la portée de la variable de fonctions. Alors que la fonction interne peut lire les variables de la fonction externe, elle ne peut pas écrire.
C'est vraiment dommage. Mais avec juste une fermeture en lecture seule, vous pouvez au moins implémenter le motif décorateur de fonctions pour lequel Python offre du sucre syntaxique.
Mettre à jour
Comme il a été souligné, il existe des moyens de gérer les limitations de la portée de python et j'en exposerai certaines.
1. Utilisez le mot clé global
(généralement non recommandé).
2. Définir une classe modifiable simple Object
class Object(object):
pass
et créer un Object scope
dans initCounter
pour stocker les variables
def initCounter ():
scope = Object()
scope.x = 0
def counter():
scope.x += 1
print scope.x
return counter
Puisque scope
n’est en réalité qu’une référence, les actions effectuées avec ses champs ne modifient pas vraiment scope
en elle-même, aucune erreur ne se produit.
3. Une autre manière, comme @unutbu l'a souligné, serait de définir chaque variable comme un tableau (x = [0]
) et de modifier son premier élément (x[0] += 1
). Là encore, aucune erreur ne survient car x
n'est pas modifié.
4. Comme suggéré par @raxacoricofallapatorius, vous pourriez faire de x
une propriété de counter
def initCounter ():
def counter():
counter.x += 1
print counter.x
counter.x = 0
return counter
Python 2 n’avait pas de fermetures - des solutions de contournement qui ressemblaient à ressemblaient fermetures.
Les réponses déjà données abondent en exemples: copier des variables dans la fonction interne, modifier un objet dans la fonction interne, etc.
En Python 3, le support est plus explicite - et succinct:
def closure():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner
Usage:
start = closure()
start() # prints 1
start() # prints 2
start() # prints 3
Le mot clé nonlocal
lie la fonction interne à la variable externe explicitement mentionnée, en l’enveloppant. D'où plus explicitement une "fermeture".
J'ai eu besoin d'un espace de nom séparé mais persistant .. J'ai utilisé des classes. Je ne fais pas autrement . Les noms séparés mais persistants sont des fermetures.
>>> class f2:
... def __init__(self):
... self.a = 0
... def __call__(self, arg):
... self.a += arg
... return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16
# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16
# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1]
16
def nested1(num1):
print "nested1 has",num1
def nested2(num2):
print "nested2 has",num2,"and it can reach to",num1
return num1+num2 #num1 referenced for reading here
return nested2
Donne:
In [17]: my_func=nested1(8)
nested1 has 8
In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13
Ceci est un exemple de ce qu’est une fermeture et comment elle peut être utilisée.
J'aimerais proposer une autre comparaison simple entre l'exemple python et JS, si cela permet de clarifier les choses.
JS:
function make () {
var cl = 1;
function gett () {
console.log(cl);
}
function sett (val) {
cl = val;
}
return [gett, sett]
}
et exécutant:
a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3
Python:
def make ():
cl = 1
def gett ():
print(cl);
def sett (val):
cl = val
return gett, sett
et exécutant:
g, s = make()
g() #1
s(2); g() #1
s(3); g() #1
Raison: Comme beaucoup d'autres l'ont dit plus haut, en python, s'il existe une affectation dans la portée interne à une variable du même nom, une nouvelle référence est créée dans la portée interne. Ce n'est pas le cas avec JS, sauf si vous en déclarez explicitement un avec le mot clé var
.