Domanda

Ho letto molto sulle chiusure e penso di capirle, ma senza offuscare il quadro per me e gli altri, spero che qualcuno possa spiegare le chiusure nel modo più conciso e chiaro possibile.Sto cercando una spiegazione semplice che possa aiutarmi a capire dove e perché vorrei usarli.

È stato utile?

Soluzione

Chiusura per chiusure

  

Gli oggetti sono dati con metodi   in allegato, le chiusure sono funzioni con   dati allegati.

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

Altri suggerimenti

È semplice: una funzione che fa riferimento a variabili di un ambito contenitore, potenzialmente dopo che il flusso di controllo ha lasciato tale ambito. Quest'ultimo bit è molto 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

Nota che 12 e 4 sono "scomparsi" all'interno di feg, rispettivamente, questa caratteristica è ciò che rende f e g chiusure adeguate.

Mi piace questa definizione approssimativa e concisa :

  

Una funzione che può fare riferimento ad ambienti che non sono più attivi.

Aggiungerei

  

Una chiusura consente di associare le variabili in una funzione senza passarle come parametri .

I decoratori che accettano i parametri sono un uso comune per le chiusure. Le chiusure sono un meccanismo di implementazione comune per quel tipo di "fabbrica di funzioni". Scelgo spesso di utilizzare le chiusure nel Pattern di strategia quando la strategia viene modificata dai dati in fase di esecuzione .

In un linguaggio che consente la definizione di blocchi anonimi - ad es. Ruby, C # - le chiusure possono essere utilizzate per implementare (fino a che punto) nuove strutture di controllo. La mancanza di blocchi anonimi è tra le limitazioni delle chiusure in Python .

Ad essere sincero, capisco perfettamente le chiusure tranne che non sono mai stato chiaro su cosa sia esattamente la cosa che è la "chiusura". e cos'è così "chiusura" a proposito. Ti consiglio di smettere di cercare qualsiasi logica dietro la scelta del termine.

Comunque, ecco la mia spiegazione:

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

bar = foo()
bar()   # print 5

Un'idea chiave qui è che l'oggetto funzione restituito da foo mantiene un hook nella var locale 'x' anche se 'x' è uscito dall'ambito e dovrebbe essere defunto. Questo hook è al var stesso, non solo al valore che var aveva al momento, quindi quando viene chiamata bar, stampa 5, non 3.

Inoltre, sii chiaro che Python 2.x ha una chiusura limitata: non c'è modo di modificare 'x' all'interno di 'barra' perché scrivere 'x = bla' dichiarerebbe una 'x' locale nella barra, non assegnare a 'x 'di foo. Questo è un effetto collaterale dell'assegnazione = dichiarazione di Python. Per ovviare a questo, Python 3.0 introduce la parola chiave non locale:

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

Non ho mai sentito parlare di transazioni utilizzate nello stesso contesto in cui si spiega cos'è una chiusura e in realtà non esiste alcuna semantica delle transazioni qui.

Si chiama chiusura perché " chiude sopra " la variabile esterna (costante), ovvero non è solo una funzione ma un recinto dell'ambiente in cui è stata creata la funzione.

Nel seguente esempio, chiamando la chiusura g dopo aver cambiato x cambierà anche il valore di x all'interno di g, poiché g chiude su x:

x = 0

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


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

Ecco un tipico caso d'uso per chiusure: callback per elementi della GUI (questa sarebbe un'alternativa alla sottoclasse della classe button). Ad esempio, puoi costruire una funzione che verrà chiamata in risposta alla pressione di un pulsante e " chiudi " sulle variabili rilevanti nell'ambito padre che sono necessarie per l'elaborazione del clic. In questo modo puoi collegare interfacce piuttosto complicate dalla stessa funzione di inizializzazione, costruendo tutte le dipendenze nella chiusura.

In Python, una chiusura è un'istanza di una funzione con variabili immutabili ad essa.

In effetti, il modello di dati spiega questo nella sua descrizione dell'attributo __closure__ delle funzioni:

  

Nessuno o una tupla di celle che contengono associazioni per le variabili libere della funzione. Di sola lettura

Per dimostrarlo:

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

closure_instance = enclosure('foo')

Chiaramente, sappiamo che ora abbiamo una funzione indicata dal nome variabile closing_instance . Apparentemente, se lo chiamiamo con un oggetto, bar , dovrebbe stampare la stringa, 'foo' e qualunque sia la rappresentazione della stringa di bar .

In effetti, la stringa 'pippo' è legata all'istanza della funzione e possiamo leggerla direttamente qui, accedendo all'attributo cell_contents del primo (e solo) cella nella tupla dell'attributo __closure__ :

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

A parte questo, gli oggetti cella sono descritti nella documentazione dell'API C:

  

" Cell " gli oggetti vengono utilizzati per implementare variabili a cui fanno riferimento multipli   oscilloscopi

E possiamo dimostrare l'utilizzo della nostra chiusura, notando che 'foo' è bloccato nella funzione e non cambia:

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

E nulla può cambiarlo:

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

Funzioni parziali

L'esempio fornito usa la chiusura come funzione parziale, ma se questo è il nostro unico obiettivo, lo stesso obiettivo può essere raggiunto con 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

Esistono anche chiusure più complicate che non rientrerebbero nell'esempio di funzione parziale e le dimostrerò ulteriormente quando il tempo lo consente.

Ecco un esempio di chiusure 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

I criteri che devono essere soddisfatti dalle Chiusure sono:

  1. Dobbiamo avere la funzione nidificata.
  2. La funzione nidificata deve fare riferimento al valore definito nella funzione che racchiude.
  3. La funzione racchiusa deve restituire la funzione nidificata.

# 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

Per me, "quotazioni chiusure" sono funzioni in grado di ricordare l'ambiente in cui sono state create. Questa funzionalità consente di utilizzare variabili o metodi all'interno della chiusura che, altrimenti, non sarebbero in grado di utilizzare perché non esistono più o sono fuori portata a causa dell'ambito. Diamo un'occhiata a questo codice in 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"  

funziona anche quando entrambi, "moltiplica" metodo e " x " variabile, non esistono più. Tutto perché la capacità di chiusura da ricordare.

abbiamo usato tutti Decoratori in Python. Sono dei buoni esempi per mostrare quali sono le funzioni di chiusura in 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)

qui il valore finale è 12

Qui, la funzione wrapper è in grado di accedere all'oggetto func perché wrapper è "chiusura lessicale", può accedere ai suoi attributi parent. Questo è il motivo per cui è in grado di accedere all'oggetto func.

Vorrei condividere il mio esempio e una spiegazione sulle chiusure. Ho fatto un esempio di pitone e due figure per dimostrare gli stati dello stack.

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!')

L'output di questo codice sarebbe il seguente:

*****      hello      #####

      good bye!    ♥♥♥

Ecco due figure per mostrare le pile e la chiusura attaccata all'oggetto funzione.

quando la funzione viene restituita dal produttore

quando la funzione viene chiamata in seguito

Quando la funzione viene chiamata attraverso un parametro o una variabile nonlocale, il codice necessita di associazioni di variabili locali come margin_top, padding e a, b, n. Al fine di garantire il funzionamento del codice funzione, il frame dello stack della funzione maker che è stato rimosso molto tempo fa dovrebbe essere accessibile, di cui è stato eseguito il backup nella chiusura che possiamo trovare insieme all'oggetto funzione del messaggio.

La migliore spiegazione che io abbia mai visto di una chiusura è stata quella di spiegare il meccanismo. È andato qualcosa del genere:

Immagina il tuo stack di programma come un albero degenerato in cui ogni nodo ha solo un figlio e il singolo nodo foglia è il contesto della tua procedura attualmente in esecuzione.

Ora allenta il vincolo che ogni nodo può avere un solo figlio.

Se lo fai, puoi avere un costrutto ('yield') che può tornare da una procedura senza scartare il contesto locale (cioè non lo fa uscire dallo stack quando ritorni). Al successivo richiamo della procedura, l'invocazione raccoglie il vecchio frame dello stack (albero) e continua l'esecuzione dal punto in cui era stata interrotta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top