Question

J'ai beaucoup lu sur les fermetures et je pense les comprendre, mais sans obscurcir la situation pour moi-même et pour les autres, j'espère que quelqu'un pourra expliquer les fermetures aussi succinctement et clairement que possible.Je recherche une explication simple qui pourrait m'aider à comprendre où et pourquoi je voudrais les utiliser.

Était-ce utile?

La solution

Fermeture sur fermetures

Les objets sont des données avec des méthodes jointes, les fermetures sont des fonctions avec des données jointes.

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2

Autres conseils

C'est simple:Une fonction qui fait référence aux variables d'une portée conteneur, potentiellement après que le flux de contrôle a quitté cette portée.Ce dernier morceau est très utile :

>>> def makeConstantAdder(x):
...     constant = x
...     def adder(y):
...         return y + constant
...     return adder
... 
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7

Notez que 12 et 4 ont "disparu" respectivement à l'intérieur de f et g, cette caractéristique est ce qui fait que f et g sont des fermetures appropriées.

J'aime cette définition approximative et succincte:

Une fonction qui peut faire référence à des environnements qui ne sont plus actifs.

j'ajouterais

Une fermeture vous permet de lier des variables dans une fonction sans les passer en paramètres.

Les décorateurs qui acceptent des paramètres sont couramment utilisés pour les fermetures.Les fermetures sont un mécanisme de mise en œuvre courant pour ce type de « fabrique de fonctions ».Je choisis fréquemment d'utiliser des fermetures dans le Modèle de stratégie lorsque la stratégie est modifiée par les données au moment de l'exécution.

Dans un langage qui permet la définition de blocs anonymes - par exemple Ruby, C# - les fermetures peuvent être utilisées pour implémenter (à quel point) de nouvelles structures de contrôle.Le manque de blocs anonymes fait partie des les limites des fermetures en Python.

Pour être honnête, je comprends parfaitement les fermetures, sauf que je n'ai jamais été clair sur ce qu'est exactement la "fermeture" et ce qu'est cette "fermeture".Je vous recommande de renoncer à chercher une quelconque logique derrière le choix du terme.

Quoi qu'il en soit, voici mon explication :

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

Une idée clé ici est que l'objet fonction renvoyé par foo conserve un hook vers la variable locale 'x' même si 'x' est hors de portée et devrait être disparu.Ce hook concerne la var elle-même, pas seulement la valeur que var avait à l'époque, donc lorsque bar est appelé, il affiche 5, pas 3.

Sachez également que Python 2.x a une fermeture limitée :il n'y a aucun moyen pour moi de modifier 'x' à l'intérieur de 'bar' car écrire 'x = bla' déclarerait un 'x' local dans la barre, pas l'attribuer au 'x' de foo.Il s'agit d'un effet secondaire de la mission assign=declaration de Python.Pour contourner ce problème, Python 3.0 introduit le mot-clé nonlocal :

def foo():
   x = 3
   def bar():
      print x
   def ack():
      nonlocal x
      x = 7
   x = 5
   return (bar, ack)

bar, ack = foo()
ack()   # modify x of the call to foo
bar()   # print 7

Je n'ai jamais entendu parler de transactions utilisées dans le même contexte pour expliquer ce qu'est une fermeture et il n'y a vraiment aucune sémantique de transaction ici.

C'est ce qu'on appelle une fermeture car elle "ferme" la variable extérieure (constante) - c'est-à-dire qu'il ne s'agit pas seulement d'une fonction mais d'une enceinte de l'environnement dans lequel la fonction a été créée.

Dans l'exemple suivant, appeler la fermeture g après avoir modifié x modifiera également la valeur de x dans g, puisque g se ferme sur x :

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

Voici un cas d'utilisation typique des fermetures : rappels pour les éléments de l'interface graphique (ce serait une alternative au sous-classement de la classe des boutons).Par exemple, vous pouvez construire une fonction qui sera appelée en réponse à une pression sur un bouton et « fermer » les variables pertinentes dans la portée parent qui sont nécessaires au traitement du clic.De cette façon, vous pouvez connecter des interfaces assez compliquées à partir de la même fonction d'initialisation, en intégrant toutes les dépendances dans la fermeture.

En Python, une fermeture est une instance d’une fonction à laquelle des variables sont liées de manière immuable.

En fait, le le modèle de données explique cela dans sa description des fonctions' __closure__ attribut:

Aucun ou un tuple de cellules qui contiennent des liaisons pour les variables libres de la fonction.Lecture seulement

Pour démontrer cela :

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

De toute évidence, nous savons que nous avons maintenant une fonction pointée à partir du nom de la variable closure_instance.Apparemment, si nous l'appelons avec un objet, bar, il devrait imprimer la chaîne, 'foo' et quelle que soit la représentation sous forme de chaîne de bar est.

En fait, la chaîne 'foo' est lié à l'instance de la fonction, et nous pouvons le lire directement ici, en accédant au cell_contents attribut de la première (et unique) cellule du tuple du __closure__ attribut:

>>> closure_instance.__closure__[0].cell_contents
'foo'

En passant, les objets de cellule sont décrits dans la documentation de l'API C :

Les objets "cellule" sont utilisés pour implémenter des variables référencées par plusieurs lunettes

Et nous pouvons démontrer l'utilisation de notre fermeture, en notant que 'foo' est bloqué dans la fonction et ne change pas :

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

Et rien ne peut le changer :

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Fonctions partielles

L'exemple donné utilise la fermeture comme fonction partielle, mais si c'est notre seul objectif, le même objectif peut être atteint avec functools.partial

>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux

Il existe également des fermetures plus compliquées qui ne correspondraient pas à l'exemple de la fonction partielle, et je les démontrerai davantage si le temps le permet.

Voici un exemple de fermetures Python3

def closure(x):
    def counter():
        nonlocal x
        x += 1
        return x
    return counter;

counter1 = closure(100);
counter2 = closure(200);

print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))

# result

i from closure 1 101
i from closure 1 102
i from closure 2 201
i from closure 1 103
i from closure 1 104
i from closure 1 105
i from closure 2 202
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

# Defining a closure

# This is an outer function.
def outer_function(message):
    # This is an inner nested function.
    def inner_function():
        print(message)
    return inner_function

# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes 
# even if they are not present in memory is called closures

# Output: Hello

Les critères à respecter par les fermetures sont :

  1. Nous devons avoir une fonction imbriquée.
  2. La fonction imbriquée doit faire référence à la valeur définie dans la fonction englobante.
  3. La fonction englobante doit renvoyer la fonction imbriquée.

# Example 2
def make_multiplier_of(n): # Outer function
    def multiplier(x): # Inner nested function
        return x * n
    return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) #  6

Pour moi, les « fermetures » sont des fonctions capables de se souvenir de l'environnement dans lequel elles ont été créées.Cette fonctionnalité vous permet d'utiliser des variables ou des méthodes dans la fermeture que, d'une autre manière, vous ne pourriez pas utiliser soit parce qu'elles n'existent plus, soit parce qu'elles sont hors de portée en raison de la portée.Regardons ce code en Ruby :

def makefunction (x)
  def multiply (a,b)
    puts a*b
  end
  return lambda {|n| multiply(n,x)} # => returning a closure
end

func = makefunction(2) # => we capture the closure
func.call(6)    # => Result equal "12"  

cela fonctionne même lorsque la méthode "multiplier" et la variable "x" n'existent plus.Tout cela parce que la capacité de fermeture est à retenir.

nous avons tous utilisé Décorateurs en python.Ce sont de bons exemples pour montrer ce que sont les fonctions de fermeture en python.

class Test():
    def decorator(func):
        def wrapper(*args):
            b = args[1] + 5
            return func(b)
        return wrapper

@decorator
def foo(val):
    print val + 2

obj = Test()
obj.foo(5)

ici la valeur finale est 12

Ici, la fonction wrapper est capable d'accéder à l'objet func car le wrapper est une "fermeture lexicale", elle peut accéder à ses attributs parents.C'est pourquoi, il est capable d'accéder à l'objet func.

Je voudrais partager mon exemple et une explication sur les fermetures.J'ai fait un exemple python et deux figures pour démontrer les états de la pile.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

Le résultat de ce code serait le suivant :

*****      hello      #####

      good bye!    ♥♥♥

Voici deux figures pour montrer les piles et la fermeture attachée à l'objet fonction.

lorsque la fonction est renvoyée par le fabricant

quand la fonction est appelée plus tard

Lorsque la fonction est appelée via un paramètre ou une variable non locale, le code a besoin de liaisons de variables locales telles que margin_top, padding ainsi que a, b, n.Afin de garantir le fonctionnement du code de fonction, le cadre de pile de la fonction maker qui a disparu depuis longtemps doit être accessible, qui est sauvegardé dans la fermeture que nous pouvons trouver avec l'objet fonction du message.

La meilleure explication que j’ai jamais vue d’une fermeture était d’expliquer le mécanisme.Cela s'est passé à peu près comme ceci :

Imaginez votre pile de programmes comme un arbre dégénéré où chaque nœud n'a qu'un seul enfant et le nœud feuille unique est le contexte de votre procédure en cours d'exécution.

Supprimez maintenant la contrainte selon laquelle chaque nœud ne peut avoir qu'un seul enfant.

Si vous faites cela, vous pouvez avoir une construction (« rendement ») qui peut revenir d'une procédure sans ignorer le contexte local (c'est-à-direil ne le retire pas de la pile à votre retour).La prochaine fois que la procédure est invoquée, l'invocation récupère l'ancienne trame de pile (arborescence) et continue l'exécution là où elle s'est arrêtée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top