2015-12-23 12 views
21

Il seguente codice solleva un UnboundLocalError:Modifica una variabile all'interno di un metodo con un altro metodo al suo interno

def foo(): 
    i = 0 
    def incr(): 
     i += 1 
    incr() 
    print(i) 

foo() 

C'è un modo per ottenere questo risultato?

+4

È possibile passare 'i' come argomento. – Maroun

+1

È possibile aggiornare il valore di 'i' come' i = incr() ' –

+2

@MarounMaroun Judgind da questa domanda, cosa Python, non ha chiusure? 0_o 'Non mi è familiare, però. O è perché 'def' definisce una funzione nel namespace globale? Sono solo curioso e non voglio imparare Python solo per questo. :) – hijarian

risposta

9

Vedi 9.2. Python Scopes and Namespaces:

se non global affermazione è in effetto - le assegnazioni ai nomi vanno sempre nello scopo più interno.

anche:

Il global dichiarazione può essere utilizzato per indicare che particolari variabili vivono nello scope globale e dovrebbero essere rimbalzo lì; l'istruzione nonlocal indica che determinate variabili vivono in un ambito recintato e dovrebbero essere rimbalzate lì.

hai molte soluzioni:

  • Passo i come ✓ argomento (vorrei andare con questo)
  • Usa nonlocal parola chiave

Si noti che in python2.X voi può accedere a variabili non locali ma non è possibile modificare .

20

È possibile utilizzare i come argomento come questo:

def foo(): 
    i = 0 
    def incr(i): 
     return i + 1 
    i = incr(i) 
    print(i) 

foo() 
+6

Ottima risposta, poiché questo non dipende da Python 3, come le altre soluzioni 'nonlocal'. –

+2

Il nome 'incr' non è ben scelto per una funzione che restituisce uno più del suo argomento senza effetti collaterali. –

+0

Il nome di questa versione funzionale di 'incr' dovrebbe probabilmente essere' succ' :) –

4

In Python, int sono immutabili. Quindi potresti inserire il tuo int in un oggetto mutabile.

def foo(): 
    i = 0 
    obj = [i] 
    def incr(obj): 
     obj[0]+=1 
    incr(obj) 
    print(obj[0]) 

foo() 
+1

Con questo approccio, non è necessario passare 'obj' come argomento a' incr'; funziona bene come chiusura. – tsbertalan

2

È possibile rendere globale i e utilizzarlo.

i = 0 
def foo(): 
    def incr(): 
     global i 
     i += 1 
    incr() 
    print(i) 

foo() 

ma il modo più preferito è quello di passare i come param per incr

def foo(): 
    i = 0 
    def incr(arg): 
     arg += 1 
     return arg 
    i = incr(i) 
    print(i) 

foo() 
+0

@BurhanKhalid su test sembra funzionare, si prega di indicare se ho perso qualcosa. – anand

6

persone hanno risposto alla tua domanda, ma nessuno sembra affrontare perché proprio questo sta accadendo.

Il seguente codice solleva un UnboundLocalError

Allora, perché ?Diamo una citazione dal FAQ:

Quando si effettua un'assegnazione ad una variabile in un campo di applicazione, che variabile diventa locale per tale ambito e ombre qualsiasi variabile nome simile nel perimetro esterno.

All'interno della funzione annidata si sta eseguendo un incarico con l'operatore +=. Ciò significa cheeseguirà approssimativamente i = i + 1 (da una prospettiva vincolante). Di conseguenza, nell'espressione i + 1 verrà ricercata nell'ambito locale (poiché viene utilizzata nell'istruzione di assegnazione) per la funzione dove non verrà trovata, con conseguente UnboundLocalError: reference before assignment.

Ci sono molti modi per affrontare questo e Python 3 è più elegante nell'approccio si può prendere di Python 2.

Python 3 nonlocal:

I nonlocal dichiarazioni dice a Python per cercare un nome in l'ambito racchiude (quindi in questo caso, nell'ambito della funzione foo()) per nome riferimenti:

def foo(): 
    i = 0 
    def incr(): 
     nonlocal i 
     i +=1 
    incr() 
    print(i) 

Function attributes Python 2.x and 3.x:

Ricordare che le funzioni sono oggetti di prima classe, in quanto tali possono memorizzare lo stato. Usa gli attributi della funzione per accedere e modificare lo stato della funzione, la cosa buona con questo approccio è che funziona su tutti i pitoni e non richiede le dichiarazioni global, nonlocal.

def foo(): 
    foo.i = 0 
    def incr(): 
     foo.i +=1 
    incr() 
    print(foo.i) 

The global Statement (Python 2.x, 3.x):

davvero il più brutto del gruppo, ma ottiene il lavoro fatto:

i = 0 
def foo(): 
    def incr(): 
     global i 
     i += 1 
    incr() 
    print(i) 

foo() 

La possibilità di passare un argomento alla funzione ottiene lo stesso risultato ma non si riferisce alla mutazione del campo di applicazione nel senso nonlocal e global sono ed è più simile agli attributi di funzione presentati. Sta creando una nuova variabile locale nella funzione inc e quindi re-binding il nome in i con lo i = incr(i) ma, in effetti, ha completato il lavoro.

0

si potrebbe anche usare una funzione lambda:

def foo(): 
    i=0 
    incr = lambda x: x+1 
    print incr(i) 

foo() 

Credo che il codice è più pulito in questo modo

0

funzione semplice attributi non funziona in questo caso.

>>> def foo(): 
...  foo.i = 0 
...  def incr(): 
...   foo.i +=1 
...  incr() 
...  print(foo.i) 
... 
>>> 
>>> 
>>> foo() 
1 
>>> foo() 
1 
>>> foo() 
1 

si ri assegnare il foo.i per 0 per ogni chiamata di foo. È meglio usare hassattr. Ma il codice diventa più complicato.

>>> def foo(): 
...  if not hasattr(foo, 'i'): 
...   foo.i = 0 
...  def incr(): 
...   foo.i += 1 
...   return foo.i 
...  i = incr() 
...  print(i) 
... 
>>> 
>>> 
>>> foo() 
1 
>>> foo() 
2 
>>> foo() 
3 

Inoltre è possibile provare questa idea:

>>> def foo(): 
...  def incr(): 
...   incr.i += 1 
...   return incr.i 
...  incr.i = 0 
...  return incr 
... 
>>> a = foo() 
>>> a() 
1 
>>> a() 
2 
>>> a() 
3 
>>> a() 
4 

può essere è più a portata di mano per avvolgervi INCR in decoratore.

Problemi correlati