2010-10-26 21 views
192

Ho visto e utilizzato funzioni nidificate in Python e corrispondono alla definizione di chiusura. Allora perché si chiamano nested functions invece di closures?Perché le funzioni nidificate di Python non sono chiamate chiusure?

Le funzioni nidificate non sono chiuse perché non vengono utilizzate dal mondo esterno?

UPDATE: Stavo leggendo di chiusure e mi ha fatto riflettere su questo concetto rispetto a Python. Ho cercato e trovato l'articolo menzionato da qualcuno in un commento qui sotto, ma non ho potuto capire completamente la spiegazione in quell'articolo, quindi è per questo che sto facendo questa domanda.

+8

È interessante notare che alcuni googling mi hanno trovato questo, datato dicembre 2006: http://effbot.org/zone/closure.htm. Non sono sicuro-sono "duplicati esterni" disapprovati su SO? – hbw

+1

[PEP 227 - Scope nidificate staticamente] (http://www.python.org/dev/peps/pep-0227/) per ulteriori informazioni. –

risposta

305

Una chiusura si verifica quando una funzione ha accesso a una variabile locale da un ambito che ha terminato la sua esecuzione.

def make_printer(msg): 
    def printer(): 
     print msg 
    return printer 

printer = make_printer('Foo!') 
printer() 

Quando make_printer viene chiamato, un nuovo telaio è messo in pila con il codice compilato per la funzione printer come costante e il valore di msg come un locale. Quindi crea e restituisce la funzione. Poiché la funzione printer fa riferimento alla variabile msg, viene mantenuta attiva dopo che è stata restituita la funzione make_printer.

Quindi, se le funzioni nidificate non lo fanno

  1. variabili di accesso che sono locali a scopi che racchiudono,
  2. farlo quando vengono eseguiti al di fuori di tale ambito,

poi non sono chiusure.

Ecco un esempio di una funzione nidificata che non è una chiusura.

def make_printer(msg): 
    def printer(msg=msg): 
     print msg 
    return printer 

printer = make_printer("Foo!") 
printer() #Output: Foo! 

Qui, stiamo vincolando il valore al valore predefinito di un parametro. Ciò si verifica quando viene creata la funzione printer e quindi non è necessario mantenere alcun riferimento al valore di msg esterno a printer dopo i ritorni make_printer. msg è solo una normale variabile locale della funzione printer in questo contesto.

+1

La tua risposta è molto meglio della mia, sei un buon punto, ma se dovessimo passare alle più rigide definizioni di programmazione funzionale, i tuoi esempi sono anche funzioni? È passato un po 'di tempo e non ricordo se una rigida programmazione funzionale consente funzioni che non restituiscono valori. Il punto è discutibile, se si considera il valore restituito come Nessuno, ma questo è un argomento completamente diverso. – mikerobi

+5

@mikerobi, non sono sicuro che abbiamo bisogno di prendere in considerazione la programmazione funzionale dal momento che Python non è realmente un linguaggio funzionale anche se certamente può essere usato come tale. Ma no, le funzioni interne non sono funzioni in quel senso poiché il loro intero punto è creare effetti collaterali. È facile creare una funzione che illustri i punti anche se, – aaronasterling

+19

@mikerobi: Se un blob di codice è o meno una chiusura dipende dal fatto che si chiuda o meno sul suo ambiente, non come lo chiami. Potrebbe essere una routine, una funzione, una procedura, un metodo, un blocco, una subroutine, qualsiasi cosa. In Ruby, i metodi non possono essere chiusure, solo i blocchi possono. In Java, i metodi non possono essere chiusure, ma le classi possono. Questo non li rende meno di una chiusura. (Sebbene il fatto che chiudano solo * alcune * variabili, e non possano modificarle, le rende quasi inutili.) Si potrebbe obiettare che un metodo è solo una procedura chiusa su 'self'. (In JavaScript/Python è quasi vero) –

79

La questione è già stato risposto conaaronasterling

Tuttavia, qualcuno potrebbe essere interessato a come le variabili sono memorizzate sotto il cofano.

Prima di arrivare al frammento:

chiusure sono funzioni che ereditano le variabili dal loro ambiente che racchiude. Quando si passa una funzione callback come argomento a un'altra funzione che eseguirà I/O, questa funzione di callback verrà richiamata in un secondo momento e questa funzione - quasi magicamente - ricorderà il contesto in cui è stata dichiarata, insieme a tutte le variabili disponibili in quel contesto.

  • Se una funzione non utilizza variabili libere, non forma una chiusura.

  • Se c'è un altro livello interno che utilizza variabili libere - tutti livelli precedenti salvare l'ambiente lessicale (esempio alla fine)

  • funzione attributi func_closure in pitone < 3.X o __closure__ in python> 3.X salva le variabili libere.

  • Ogni funzione in python ha questi attributi di chiusura, ma non salva alcun contenuto se non ci sono variabili libere.

esempio: di attributi di chiusura ma non contenuto all'interno quanto non v'è alcuna variabile libera.

NB: FREE VARIABLE è necessario per CREARE UNA CHIUSURA.

spiegherò usando lo stesso frammento di codice di cui sopra:

>>> def make_printer(msg): 
...  def printer(): 
...   print msg 
...  return printer 
... 
>>> printer = make_printer('Foo!') 
>>> printer() #Output: Foo! 

e tutte le funzioni in Python hanno una chiusura attributo Quindi cerchiamo di esaminare le variabili di cinta associati a una funzione di chiusura.

Ecco l'attributo func_closure per la funzione printer

>>> 'func_closure' in dir(printer) 
True 
>>> printer.func_closure 
(<cell at 0x108154c90: str object at 0x108151de0>,) 
>>> 

L'attributo closure restituisce una tupla di oggetti cellulari che contengono dettagli delle variabili definite nell'ambito racchiude.

Il primo elemento nella func_closure che potrebbe essere None o una tupla di celle che contengono binding per le variabili libere della funzione ed è di sola lettura.

>>> dir(printer.func_closure[0]) 
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__', 
'__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents'] 
>>> 

Qui nell'output sopra potete vedere cell_contents, vediamo cosa memorizza:

>>> printer.func_closure[0].cell_contents 
'Foo!'  
>>> type(printer.func_closure[0].cell_contents) 
<type 'str'> 
>>> 

Così, quando abbiamo chiamato la funzione printer(), si accede al valore memorizzato all'interno del cell_contents. Ecco come abbiamo ottenuto l'output come "Foo!"

Ancora una volta vi spiegherò utilizzando il frammento di cui sopra con alcune modifiche:

>>> def make_printer(msg): 
...  def printer(): 
...   pass 
...  return printer 
... 
>>> printer = make_printer('Foo!') 
>>> printer.func_closure 
>>> 

Nel frammento di cui sopra, ho din't msg di stampa all'interno della funzione di stampa, in modo che non crea alcun variabile libera. Dato che non ci sono variabili libere, non ci sarà contenuto all'interno della chiusura. Questo è esattamente ciò che vediamo sopra.

Ora vi spiegherò un altro frammento di diverso per cancellare tutto Free Variable con 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') 

Quindi, vediamo che una proprietà func_closure è una tupla di chiusura cellule, siamo in grado di loro e il loro contenuto si riferiscono esplicitamente - una cellula ha proprietà "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 
>>> 

Qui quando abbiamo chiamato inn, si farà riferimento tutta la varia gratis Bles in modo da ottenere I am free variable

>>> inn('variable') 
'I am free variable' 
>>> 
+6

In Python 3, 'func_closure' è ora chiamato' __closure__', in modo simile ai vari altri attributi 'func_ *' – lvc

+0

Grazie per la correzione –

+2

Anche '__closure_' è disponibile in Python 2.6+ per compatibilità con Python 3. – Pierre

5
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 

Dà:

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 

Questo è un esempio di ciò che una chiusura è e come può essere utilizzato.

58

Python ha un debole supporto per la chiusura. Per capire cosa intendo prendere il seguente esempio di un contatore con chiusura con 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 chiusura è molto elegante in quanto fornisce funzioni scritte in questo modo la possibilità di avere "memoria interna". A partire da Python 2.7 questo non è possibile. Se provi

def initCounter(): 
    x = 0; 
    def counter(): 
     x += 1 ##Error, x not defined 
     print x 
    return counter 

count = initCounter(); 

count(); ##Error 
count(); 
count(); 

Riceverai un errore che dice che x non è definito. Ma come può essere se è stato dimostrato da altri che è possibile stamparlo? Questo è dovuto al modo in cui Python gestisce l'ambito delle funzioni variabili. Mentre la funzione interna può leggere le variabili della funzione esterna, non è possibile scrivere .

Questo è davvero un peccato. Ma con una sola chiusura di sola lettura è possibile implementare almeno lo function decorator pattern per il quale Python offre zucchero sintattico.

Aggiornamento

Come il suo stato fatto notare, ci sono modi per affrontare le limitazioni portata di pitone e io espongono alcuni.

1. Utilizzare la parola chiave global (in generale sconsigliata).

2. Definire una semplice classe modificabile Object

class Object(object): 
    pass 

e creare un Object scope entro initCounter per memorizzare le variabili

def initCounter(): 
    scope = Object() 
    scope.x = 0 
    def counter(): 
     scope.x += 1 
     print scope.x 

    return counter 

Dal scope è in realtà solo un riferimento, le azioni intraprese con il suo i campi in realtà non modificano lo stesso scope, quindi non si verifica alcun errore.

3. Un modo alternativo, come sottolineato da @unutbu, consiste nel definire ciascuna variabile come una matrice (x = [0]) e modificarne il primo elemento (x[0] += 1). Anche in questo caso non si verifica alcun errore perché lo stesso x non viene modificato.

4. Come suggerito da @raxacoricofallapatorius, si potrebbe fare x una proprietà di counter

def initCounter(): 

    def counter(): 
     counter.x += 1 
     print counter.x 

    counter.x = 0 
    return counter 
+20

Ci sono In Python2, potresti rendere 'x = [0]' nell'oscurità esterna, e usare 'x [0] + = 1' nello scope interno. In Python3, tu potrebbe mantenere il tuo codice così com'è e utilizzare la [parola chiave non locale] (http://stackoverflow.com/a/1261952/190597). – unutbu

+0

"Mentre la funzione interna può leggere le variabili della funzione esterna, non può scriverle." - Questo è inaccurato come da commento di unutbu. Il problema è che quando Python incontra qualcosa come x = ..., x viene interpretato come una variabile locale, che ovviamente non è ancora definita in quel punto. OTOH, se x è un oggetto mutabile con un metodo mutabile, può essere modificato bene, ad es. se x è un oggetto che supporta il metodo inc() che si muta da solo, x.inc() funzionerà senza intoppi. –

+0

@ThanhDK Ciò non significa che non è possibile scrivere sulla variabile? Quando si usa chiamare un metodo da un oggetto mutabile, si sta solo dicendo di modificarlo, * non si sta effettivamente * modificando la variabile (che contiene semplicemente un riferimento all'oggetto). In altre parole, il riferimento a cui punta la variabile 'x' rimane esattamente lo stesso anche se si chiama' inc() 'o qualsiasi altra cosa, e non si è scritto in modo efficace alla variabile. – user193130

8

ho avuto una situazione in cui avevo bisogno di uno spazio nome separato ma persistente. Ho usato le classi. Io non altrimenti I nomi segregati ma persistenti sono chiusure.

>>> 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 
+0

+1 per originalità. –

6

Python 2 non ha avuto chiusure - aveva soluzioni alternative che assomigliava chiusure.

Ci sono molti esempi in risposte già dato - copia nelle variabili alla funzione interna, modifica di un oggetto sulla funzione interna, ecc

In Python 3, il supporto è più esplicito - e concisa:

def closure(): 
    count = 0 
    def inner(): 
     nonlocal count 
     count += 1 
     print(count) 
    return inner 

utilizzati:

start = closure() 
start() # prints 1 
start() # prints 2 
start() # prints 3 

il nonlocal parola lega la funzione interna alla variabile esterno esplicitamente menzionati, in ef perfetto racchiudendolo. Quindi più esplicitamente una "chiusura".

0

Mi piacerebbe offrire un altro semplice confronto tra Python e JS esempio, se questo aiuta a chiarire le cose.

JS:

function make() { 
    var cl = 1; 
    function gett() { 
    console.log(cl); 
    } 
    function sett (val) { 
    cl = val; 
    } 
    return [gett, sett] 
} 

ed esecuzione:

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 

ed esecuzione:

g, s = make() 
g() #1 
s(2); g() #1 
s(3); g() #1 

Motivo: Come molti altri hanno detto sopra, in python, se c'è un compito nello scope interno a una variabile con lo stesso nome, viene creato un nuovo riferimento nello scope interno. Non così con JS, a meno che non si dichiari esplicitamente uno con la parola chiave var.

Problemi correlati