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 car elles ne sont pas utilisées par le monde extérieur?
MISE À JOUR: Je lisais sur les fermetures et cela m'a fait penser à 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 pouvais pas comprendre complètement l'explication de cet article, c'est pourquoi je pose cette question.
python
closures
nested-function
Srikar Appalaraju
la source
la source
Réponses:
Une fermeture se produit lorsqu'une fonction a accès à une variable locale à partir d'une étendue englobante qui a terminé son exécution.
Quand
make_printer
est appelé, un nouveau cadre est placé sur la pile avec le code compilé pour laprinter
fonction en tant que constante et la valeur de enmsg
tant que local. Il crée et renvoie ensuite la fonction. Étant donné que la fonction faitprinter
référence à lamsg
variable, elle est maintenue en vie après lemake_printer
retour de la fonction.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.
Ici, nous lions la valeur à la valeur par défaut d'un paramètre. Cela se produit lorsque la fonction
printer
est créée et donc aucune référence à la valeur demsg
externe à neprinter
doit être conservée après lesmake_printer
retours.msg
est juste une variable locale normale de la fonctionprinter
dans ce contexte.la source
self
. (En JavaScript / Python, c'est presque vrai.)i
] d'une portée englobante". fait référence, c.-à-d. peut inspecter (ou changer)i
la valeur de, même si / quand cette portée "a terminé son exécution", c.-à-d. que l'exécution d'un programme est passée à d'autres parties du code. Le bloc oùi
est défini n'est plus, mais les fonctions auxquelles il est fait référencei
peuvent toujours le faire. Ceci est communément décrit comme «fermer la variablei
». Pour ne pas traiter les variables spécifiques, il peut être implémenté comme se fermant sur tout le cadre d'environnement où cette variable est définie.La question a déjà été répondue par aaronasterling
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 englobant. Lorsque vous passez un rappel de fonction comme argument à une autre fonction qui fera des E / S, cette fonction de rappel sera invoquée plus tard, et cette fonction se souviendra - presque comme par magie - 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
en 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 à l'intérieur car il n'y a pas de variable libre.
NB: UNE VARIABLE GRATUITE EST OBLIGATOIRE POUR CRÉER UNE FERMETURE.
Je vais vous expliquer en utilisant le même extrait que ci-dessus:
Et toutes les fonctions Python ont un attribut de fermeture, examinons donc les variables englobantes associées à une fonction de fermeture.
Voici l'attribut
func_closure
de la fonctionprinter
L'
closure
attribut renvoie un tuple d'objets de cellule qui contiennent des détails des variables définies dans la portée englobante.Le premier élément de func_closure qui pourrait être None ou un tuple de cellules contenant des liaisons pour les variables libres de la fonction et il est en lecture seule.
Ici, dans la sortie ci-dessus, vous pouvez voir
cell_contents
, voyons ce qu'il stocke:Ainsi, lorsque nous avons appelé la fonction
printer()
, elle accède à la valeur stockée à l'intérieur ducell_contents
. C'est ainsi que nous avons obtenu la sortie en tant que 'Foo!'Je vais à nouveau expliquer l'utilisation de l'extrait ci-dessus avec quelques modifications:
Dans l'extrait ci-dessus, je n'imprime pas le msg à l'intérieur de la fonction imprimante, donc 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.
Je vais maintenant expliquer un autre extrait différent pour tout effacer
Free Variable
avecClosure
:Donc, nous voyons qu'une
func_closure
propriété est un tuple de cellules de fermeture , nous pouvons les référencer explicitement et leur contenu - une cellule a la propriété "cell_contents"Ici, lorsque nous avons appelé
inn
, il fera référence à toutes les variables de sauvegarde gratuites afin que nous obtenionsI am free variable
la source
func_closure
est maintenant appelé__closure__
, de la même manière que les divers autresfunc_*
attributs.__closure_
disponible en Python 2.6+ pour la compatibilité avec Python 3.__closure__
objet qui est la fermeture.Python a un faible support pour la fermeture. Pour voir ce que je veux dire, prenons l'exemple suivant d'un compteur utilisant la fermeture avec JavaScript:
La fermeture est assez élégante car elle donne aux fonctions écrites comme celle-ci la possibilité d'avoir une "mémoire interne". Depuis Python 2.7, cela n'est pas possible. Si tu essayes
Vous obtiendrez une erreur indiquant que x n'est pas défini. Mais comment est-ce possible 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 variable des fonctions. Alors que la fonction intérieure peut lire les variables de la fonction externe, elle ne peut pas les écrire .
C'est vraiment dommage. Mais avec une fermeture en lecture seule, vous pouvez au moins implémenter le modèle de décorateur de fonctions pour lequel Python offre du sucre syntaxique.
Mettre à jour
Comme cela a été souligné, il existe des moyens de gérer les limitations de portée de python et je vais en exposer quelques-unes.
1. Utilisez le
global
mot clé (en général non recommandé).2. Dans Python 3.x, utilisez le
nonlocal
mot - clé (suggéré par @unutbu et @leewz)3. Définissez une classe modifiable simple
Object
et créer un
Object scope
intérieurinitCounter
pour stocker les variablesComme il ne
scope
s'agit que d'une référence, les actions prises avec ses champs ne se modifient pas vraimentscope
, donc aucune erreur ne se produit.4. Une manière alternative, comme l'a souligné @unutbu, serait de définir chaque variable comme un tableau (
x = [0]
) et de modifier son premier élément (x[0] += 1
). Encore une fois, aucune erreur ne se produit carx
elle-même n'est pas modifiée.5. Comme suggéré par @raxacoricofallapatorius, vous pouvez faire
x
une propriété decounter
la source
x = [0]
dans la portée externe et utiliserx[0] += 1
dans la portée interne. En Python3, vous pouvez conserver votre code tel quel et utiliser le mot-clé non local .x
pointe la variable reste exactement la même, même si vous appelezinc()
ou quoi que ce soit, et vous n'avez pas effectivement écrit dans la variable.x
une propriété decounter
.nonlocal
mot - clé, qui est commeglobal
mais pour les variables d'une fonction externe. Cela permettra à une fonction interne de relier un nom à ses fonctions externes. Je pense que "lier au nom" est plus précis que "modifier la variable".Python 2 n'avait pas de fermetures - il y avait des solutions de contournement qui ressemblaient à fermetures.
Il y a beaucoup d'exemples dans les réponses déjà données - copie de variables dans la fonction interne, modification d'un objet sur la fonction interne, etc.
Dans Python 3, le support est plus explicite - et succinct:
Usage:
Le
nonlocal
mot-clé lie la fonction interne à la variable externe explicitement mentionnée, l'enfermant en effet. D'où plus explicitement une «fermeture».la source
J'ai eu une situation où j'avais besoin d'un espace de nom séparé mais persistant. J'ai utilisé des cours. Je n'en ai pas autrement. Les noms séparés mais persistants sont des fermetures.
la source
Donne:
Ceci est un exemple de ce qu'est une fermeture et comment elle peut être utilisée.
la source
J'aimerais offrir une autre comparaison simple entre python et l'exemple JS, si cela aide à rendre les choses plus claires.
JS:
et exécuter:
Python:
et exécuter:
Motif: 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 dans la portée interne est créée. Ce n'est pas le cas avec JS, sauf si vous en déclarez explicitement un avec le
var
mot - clé.la source