2013-06-30 11 views
9

Mi chiedo come posso accedere a una funzione all'interno di un'altra funzione. Ho visto il codice come questo:Come accedere a una funzione all'interno di una funzione?

>>> def make_adder(x): 
     def adder(y): 
     return x+y 
     return adder 
>>> a = make_adder(5) 
>>> a(10) 
15 

Quindi, c'è un altro modo per chiamare la funzione adder? E la mia seconda domanda è: perché nell'ultima riga chiamo adder non adder(...)?

Buone spiegazioni sono molto apprezzate.

+0

equivalente: 'make_adder = lambda x: lambda y: x + y' ora, puoi chiamare il' lambda 'interno? – Elazar

+0

sì! 'make_adder = make_adder (...)', quindi 'make_adder (...)' – ovrwngtvity

+0

Che è, no, non è possibile. – Elazar

risposta

5

No, non è possibile chiamarlo direttamente in quanto è una variabile locale su make_adder.

È necessario utilizzare adder() perché return adder ha restituito l'oggetto funzione adder quando è stato chiamato make_adder(5). Per eseguire questo oggetto funzione è necessario ()

def make_adder(x): 
     def adder(y): 
      return x+y 
     return adder 
... 
>>> make_adder(5)    #returns the function object adder 
<function adder at 0x9fefa74> 

Qui si può chiamare direttamente perché hai accesso ad esso, come è stato restituito dalla funzione make_adder. L'oggetto restituito è in realtà chiamato closure perché anche se la funzione make_addr è già stata restituita, l'oggetto funzione adder restituito da esso può ancora accedere alla variabile x. In py3.x puoi anche modificare il valore di x usando la dichiarazione nonlocal.

>>> make_adder(5)(10)   
15 

Py3.x esempio:

>>> def make_addr(x): 
     def adder(y): 
       nonlocal x 
       x += 1 
       return x+y 
     return adder 
... 
>>> f = make_addr(5) 
>>> f(5)    #with each call x gets incremented 
11 
>>> f(5) 
12 

#g gets it's own closure, it is not related to f anyhow. i.e each call to 
# make_addr returns a new closure. 
>>> g = make_addr(5) 
>>> g(5) 
11 
>>> g(6) 
13 
+0

Vale la pena notare: poiché Python 2.x manca di "non locale", non è possibile modificare le variabili non locali come semplicemente. Tuttavia, puoi ancora creare valori modificabili usando le liste (aggiungerò una risposta qui sotto). – torek

18

Davvero non vuoi andare in questa tana del coniglio, ma se insisti, è possibile. Con un po 'di lavoro.

La funzione annidata è creato nuovo per ogni chiamata a make_adder():

>>> import dis 
>>> dis.dis(make_adder) 
    2   0 LOAD_CLOSURE    0 (x) 
       3 BUILD_TUPLE    1 
       6 LOAD_CONST    1 (<code object adder at 0x10fc988b0, file "<stdin>", line 2>) 
       9 MAKE_CLOSURE    0 
      12 STORE_FAST    1 (adder) 

    4   15 LOAD_FAST    1 (adder) 
      18 RETURN_VALUE   

Il MAKE_CLOSURE codice operativo non crea una funzione con una chiusura, una funzione annidata riferimento alla x dalla funzione genitore (il LOAD_CLOSURE codice operativo costruisce la cella di chiusura per la funzione).

Senza chiamare la funzione make_adder, è possibile accedere solo all'oggetto codice; è memorizzato come costante con il codice funzione make_adder(). Il codice byte adder conta sul fatto di poter accedere alla variabile x come una cellula ambito, tuttavia, che rende il codice oggetto quasi inutile:

>>> make_adder.__code__.co_consts 
(None, <code object adder at 0x10fc988b0, file "<stdin>", line 2>) 
>>> dis.dis(make_adder.__code__.co_consts[1]) 
    3   0 LOAD_DEREF    0 (x) 
       3 LOAD_FAST    0 (y) 
       6 BINARY_ADD   
       7 RETURN_VALUE   

LOAD_DEREF carica un valore da una cella di chiusura. Per rendere l'oggetto codice in un nuovo oggetto funzione, che avrebbe dovuto passare che al costruttore funzione di:

>>> from types import FunctionType 
>>> FunctionType(make_adder.__code__.co_consts[1], globals(), 
...    None, None, (5,)) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: arg 5 (closure) expected cell, found int 

ma come si può vedere, il costruttore si aspetta di trovare una chiusura, non è un valore intero. Per creare una chiusura, abbiamo bisogno, beh, di una funzione che abbia variabili libere; quelli contrassegnati dal compilatore come disponibili per la chiusura. E ha bisogno di restituire quei valori chiusi su di noi, non è possibile creare una chiusura altrimenti. Quindi, creiamo una funzione annidata solo per la creazione di una chiusura:

def make_closure_cell(val): 
    def nested(): 
     return val 
    return nested.__closure__[0] 

cell = make_closure_cell(5) 

Ora possiamo ricreare adder() senza chiamare make_adder:

>>> adder = FunctionType(make_adder.__code__.co_consts[1], globals(), 
...      None, None, (cell,)) 
>>> adder(10) 
15 

forse solo chiamando make_adder() sarebbe stato più semplice.

Per inciso, come potete vedere, le funzioni sono oggetti di prima classe in Python. make_adder è un oggetto e con l'aggiunta di si invoca o chiamata la funzione. In questo caso, quella funzione restituisce un altro oggetto funzione, uno che è possibile chiamare anche. Nell'esempio tortuoso sopra riportato su come creare adder() senza chiamare make_adder(), ho fatto riferimento all'oggetto funzione make_adder senza chiamarlo; per smontare il codice byte Python ad esso collegato, o per recuperare costanti o chiusure da esso, per esempio.Allo stesso modo, la funzione make_adder() restituisce l'oggetto funzione adder; il punto di make_adder() è quello di creare quella funzione per qualcos'altro per poi chiamarlo.

La sessione precedente è stata condotta tenendo conto della compatibilità tra Python 2 e 3. Le versioni precedenti di Python 2 funzionano allo stesso modo, anche se alcuni dettagli differiscono un po '; alcuni attributi hanno nomi diversi, ad esempio func_code anziché __code__, ad esempio. Consulta la documentazione su questi nello inspect module e nello Python datamodel se vuoi conoscere i dettagli nitty grintosi.

+0

Mi chiedo semplicemente: è '__code__' portabile su implementazioni, o su CPython-specifico? –

+0

@rightfold: dal [glossario sul bytecode] (http://docs.python.org/2/glossary.html#term-bytecode): * Il codice sorgente Python è compilato in bytecode, la rappresentazione interna di un programma Python in l'interprete CPython. *. Jython e IronPython hanno attributi 'func_code' ma non so cosa contenga. –

+0

"Davvero non vuoi andare in questa tana del coniglio, ma se insisti, è possibile" Questo mi ha fatto ridere. Questa è stata una spiegazione molto dettagliata. – coder543

4

Si sta restituendo la funzione adder al chiamante, non il risultato della chiamata, quindi l'assenza di parentesi.

Perché make_adder restituisce adder, si ha già accesso diretto a adder. Infatti, a(10) è in realtà una chiamata a adder(10).

1

Come un addendum al @ di AshwiniChaudhary risposta, è possibile emulare 3.x di Python non locale con gli oggetti modificabili. Per esempio:

def counter(name): 
    x = [0] 
    def inc(n): 
     x[0] += n 
     print "%s: %d" % (name, x[0]) 
    return inc 

spam = counter('spams') 
ham = counter('hams') 

spam(3) 
ham(1) 
spam(1) 
ham(2) 

In python2.7 questo produce:

$ python closure.py 
spams: 3 
hams: 1 
spams: 4 
hams: 3 

La ragione per usare x[0] è che i tentativi per destinare x creare un nuovo locale-to-incx:

def counter(name): 
    x = 0 
    def inc(n): 
     x += n # this doesn't work! 
     print "%s: %d" % (name, x[0]) 
    return inc 

Tentativo di utilizzare questo produce:

Traceback (most recent call last): 
    File "closure.py", line 11, in <module> 
    spam(3) 
    File "closure.py", line 4, in inc 
    x += n 
UnboundLocalError: local variable 'x' referenced before assignment 

La cosa ovvia rimanente, un tentativo di utilizzare global, riesce anche come si cerca di accedere ad un modulo a livello x invece di quello interno counter. (Questo è il motivo per cui è stato aggiunto nonlocal in primo luogo!)

Un altro punto sulle chiusure: sono trasformabili meccanicamente in/da classi con variabili di istanza. Invece di definire counter come sopra, ho potuto fare una classe:

class Counter(object): 
    def __init__(self, name): 
     self.name = name 
     self.x = 0 
    def inc(self, n): 
     self.x += n 
     print "%s: %d" % (self.name, self.x) 

e quindi utilizzarlo come:

spam = Counter('spams') 
spam.inc(3) 

per esempio. Se si vuole preservare la sintassi di chiamata, Python permette questo: invece di definire inc(self, n), definire __call__(self, n) -o definire __call__ come invocando inc, dando luogo a:

class Counter(object): 
    def __init__(self, name): 
     self.name = name 
     self.x = 0 
    def inc(self, n): 
     self.x += n 
     print "%s: %d" % (self.name, self.x) 
    __call__ = inc 

spam = Counter('spams') 
ham = Counter('hams') 

spam.inc(3) 
ham.inc(1) 
spam(1) 
ham(2) 

che mostra le un po 'schizofrenici "due modi per chiamare lo "interfaccia nella classe. :-)

Problemi correlati