web-dev-qa-db-fra.com

Pourquoi les fonctions imbriquées dans Python ne s'appellent-elles pas des fermetures?

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.

214
Srikar Appalaraju

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 

  1. accéder aux variables locales aux étendues englobantes, 
  2. faire ainsi quand ils sont exécutés en dehors de cette portée,

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. 

345
aaronasterling

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'
>>>
87
James Sapam

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
59
Cristian Garcia

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".

10
Lee Benson

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
9
fp_mora
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.

5
Krcn U

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.

0
forumulator