Risposte:
Le funzioni interne possono leggere variabili non locali in 2.x, ma non riassociarle . Questo è fastidioso, ma puoi aggirarlo. Basta creare un dizionario e memorizzare i dati come elementi al suo interno. Funzioni interne non sono vietate dalla mutando gli oggetti che le variabili non locali riferiscono.
Per utilizzare l'esempio di Wikipedia:
def outer():
d = {'y' : 0}
def inner():
d['y'] += 1
return d['y']
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
def inner(): print d; d = {'y': 1}
. Qui, print d
legge esterno d
creando così una variabile non locale d
nell'ambito interno.
X = 1
lega semplicemente il nome X
a un oggetto particolare ( int
e al valore 1
). X = 1; Y = X
lega due nomi allo stesso identico oggetto. Ad ogni modo, alcuni oggetti sono mutabili e puoi cambiare i loro valori.
La seguente soluzione si ispira alla risposta di Elias Zamaria , ma contrariamente a quella risposta gestisce correttamente più chiamate della funzione esterna. La "variabile"inner.y
è locale alla chiamata corrente di outer
. Solo che non è una variabile, dato che è proibito, ma un attributo di oggetto (l'oggetto è la funzione inner
stessa). Questo è molto brutto (nota che l'attributo può essere creato solo dopo che la inner
funzione è stata definita) ma sembra efficace.
def outer():
def inner():
inner.y += 1
return inner.y
inner.y = 0
return inner
f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)
inc()
e un dec()
restituito dall'esterno che incrementa e decrementa un contatore condiviso. Quindi devi decidere a quale funzione allegare il valore del contatore corrente e fare riferimento a quella funzione dall'altra (e). Che sembra un po 'strano e asimmetrico. Ad esempio in dec()
una riga come inc.value -= 1
.
Piuttosto che un dizionario, c'è meno confusione in una classe non locale . La modifica di @ ChrisB esempio :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
Poi
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Ogni chiamata outer () crea una nuova e distinta classe chiamata context (non semplicemente una nuova istanza). Quindi evita che @ Nathaniel faccia attenzione al contesto condiviso.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
e creando un oggetto invece di usare la classe, ad esempio, context.z = 3
si solleva un file AttributeError
. È possibile per tutte le classi, a meno che non ereditino da una classe che non definisce gli slot.
Penso che la chiave qui sia cosa intendi per "accesso". Non dovrebbero esserci problemi con la lettura di una variabile al di fuori dell'ambito di chiusura, ad es.
x = 3
def outer():
def inner():
print x
inner()
outer()
dovrebbe funzionare come previsto (stampa 3). Tuttavia, l'override del valore di x non funziona, ad es.
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
stamperà ancora 3. Dalla mia comprensione di PEP-3104 questo è ciò che la parola chiave non locale intende coprire. Come accennato nel PEP, puoi usare una classe per ottenere la stessa cosa (un po 'disordinato):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
seguita da ns.x = 3
. Non è carino, ma è leggermente meno brutto ai miei occhi.
class Namespace: x = 3
?
ns
un oggetto globale ed è per questo che puoi fare riferimento ns.x
a livello di modulo print
nell'istruzione alla fine .
C'è un altro modo per implementare variabili non locali in Python 2, nel caso in cui una qualsiasi delle risposte qui non sia desiderabile per qualsiasi motivo:
def outer():
outer.y = 0
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
È ridondante usare il nome della funzione nell'istruzione di assegnazione della variabile, ma mi sembra più semplice e pulito che mettere la variabile in un dizionario. Il valore viene ricordato da una chiamata all'altra, proprio come nella risposta di Chris B.
f = outer()
e successivamente lo fai g = outer()
, f
il contatore di sarà azzerato. Questo perché entrambi condividono una singola outer.y
variabile, invece di avere ciascuno la propria variabile indipendente. Sebbene questo codice sembri esteticamente più gradevole della risposta di Chris B, il suo modo sembra essere l'unico modo per emulare lo scoping lessicale se si desidera chiamare outer
più di una volta.
outer.y
non coinvolge nulla di locale alla chiamata di funzione (istanza) outer()
, ma assegna a un attributo dell'oggetto funzione che è associato al nome outer
nel suo ambito di inclusione . E quindi si potrebbe altrettanto bene usare, per iscritto outer.y
, qualsiasi altro nome invece di outer
, purché si sappia che è vincolato a tale scopo. È corretto?
outer.y
usare il nome inner.y
(poiché inner
è vincolato all'interno della chiamataouter()
, che è esattamente l'ambito che vogliamo), ma mettere l'inizializzazione inner.y = 0
dopo la definizione di interno (poiché l'oggetto deve esistere quando viene creato il suo attributo), ma ovviamente prima return inner
?
Ecco qualcosa ispirato da un suggerimento di Alois Mahdal in un commento riguardante un'altra risposta :
class Nonlocal(object):
""" Helper to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nl = Nonlocal(y=0)
def inner():
nl.y += 1
return nl.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Aggiornare
Dopo aver ripensato a questo di recente, sono rimasto colpito da quanto fosse simile a un decoratore - quando mi sono reso conto che implementarlo come se fosse uno lo avrebbe reso più generico e utile (anche se così facendo degrada in una certa misura la sua leggibilità).
# Implemented as a decorator.
class Nonlocal(object):
""" Decorator class to help implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self._vars = kwargs
def __call__(self, func):
for k, v in self._vars.items():
setattr(func, k, v)
return func
@Nonlocal(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Nota che entrambe le versioni funzionano sia in Python 2 che in 3.
C'è una verruca nelle regole di scoping di Python: l'assegnazione rende una variabile locale al suo ambito di funzione che lo racchiude immediatamente. Per una variabile globale, risolveresti questo problema con la global
parola chiave.
La soluzione è introdurre un oggetto condiviso tra i due ambiti, che contenga variabili mutabili, ma a cui si faccia riferimento tramite una variabile non assegnata.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Un'alternativa è rappresentata dall'hackery di alcuni ambiti:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Potresti essere in grado di escogitare qualche trucco per ottenere il nome del parametro outer
e quindi passarlo come varname, ma senza fare affidamento sul nome outer
vorresti che fosse necessario utilizzare un combinatore Y.
nonlocal
. locals()
crea un dizionario di outer()
s locals al momento in cui inner()
è definito ma la modifica di quel dizionario non cambia v
in outer()
. Questo non funzionerà più quando hai più funzioni interne che vogliono condividere una variabile chiusa. Pronuncia un inc()
e dec()
quell'incrementa e decrementa un contatore condiviso.
nonlocal
è una funzionalità di Python 3.
nonlocal
in Python 2 in generale . Le tue idee non coprono il caso generale ma solo quello con una funzione interna. Dai un'occhiata a questa sintesi per un esempio. Entrambe le funzioni interne hanno il proprio contenitore. Hai bisogno di un oggetto mutabile nell'ambito della funzione esterna, come già suggerito da altre risposte.
nonlocal
parola chiave introdotta in Python 3.
Un altro modo per farlo (anche se è troppo prolisso):
import ctypes
def outer():
y = 0
def inner():
ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
return y
return inner
x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
Estendendo l'elegante soluzione di Martineau sopra a un caso d'uso pratico e un po 'meno elegante ottengo:
class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
nl = nonlocals( n=0, m=1 )
def inner():
nl.n += 1
inner() # will increment nl.n
or...
sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(self, a_dict):
self.__dict__.update(a_dict)
Usa una variabile globale
def outer():
global y # import1
y = 0
def inner():
global y # import2 - requires import1
y += 1
return y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Personalmente non mi piacciono le variabili globali. Ma la mia proposta si basa sulla risposta https://stackoverflow.com/a/19877437/1083704
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
dove l'utente deve dichiarare una variabile globale ranks
, ogni volta che è necessario chiamare il file report
. Il mio miglioramento elimina la necessità di inizializzare le variabili di funzione dall'utente.
inner
, ma non puoi assegnarle, ma puoi modificarne le chiavi e i valori. Ciò evita l'uso di variabili globali.