Odpowiedzi:
Funkcje wewnętrzne mogą odczytywać zmienne nielokalne w wersji 2.x, ale nie wiążą ich ponownie. To denerwujące, ale możesz to obejść. Po prostu utwórz słownik i przechowuj w nim swoje dane jako elementy. Wewnętrzne funkcje nie mają zakazu mutowania obiektów, do których odnoszą się zmienne nielokalne.
Aby skorzystać z przykładu z Wikipedii:
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}
. Tutaj print d
odczytuje zewnętrzną, d
tworząc w ten sposób zmienną nielokalną d
w zakresie wewnętrznym.
X = 1
po prostu wiąże nazwę X
z konkretnym obiektem ( int
z wartością 1
). X = 1; Y = X
wiąże dwie nazwy z tym samym dokładnym obiektem. W każdym razie niektóre obiekty są zmienne i możesz zmienić ich wartości.
Poniższe rozwiązanie jest inspirowane odpowiedzią Eliasa Zamarii , ale w przeciwieństwie do tej odpowiedzi poprawnie obsługuje wielokrotne wywołania funkcji zewnętrznej. „Zmienna” inner.y
jest lokalna dla bieżącego wywołania outer
. Tyle tylko, że nie jest to zmienna, ponieważ jest to zabronione, ale atrybut obiektu (obiekt jest inner
samą funkcją ). Jest to bardzo brzydkie (zauważ, że atrybut można utworzyć dopiero po inner
zdefiniowaniu funkcji), ale wydaje się skuteczne.
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()
i dec()
zwrócone z zewnątrz, które zwiększają i zmniejszają wspólny licznik. Następnie musisz zdecydować, do której funkcji dołączyć bieżącą wartość licznika i odwołać się do tej funkcji z innej funkcji. Co wygląda nieco dziwnie i asymetrycznie. Np. W dec()
linii jak inc.value -= 1
.
Zamiast słownika jest mniej bałaganu w klasie nielokalnej . Modyfikowanie przykładu @ ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
Następnie
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Każde wywołanie external () tworzy nową i odrębną klasę o nazwie context (a nie tylko nową instancję). Dzięki temu unika się ostrożności @ Nathaniel na temat współdzielonego kontekstu.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
i tworząc obiekt zamiast używać klasy, na przykład context.z = 3
podniósłby plik AttributeError
. Jest to możliwe dla wszystkich klas, chyba że dziedziczą one po klasie nie definiującej slotów.
Myślę, że kluczem jest tutaj to, co rozumiesz przez „dostęp”. Nie powinno być problemu z odczytem zmiennej spoza zakresu zamknięcia, np.
x = 3
def outer():
def inner():
print x
inner()
outer()
powinien działać zgodnie z oczekiwaniami (drukowanie 3). Jednak przesłanianie wartości x nie działa, np.
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
będzie nadal drukować 3. Z mojego zrozumienia PEP-3104 właśnie to ma obejmować słowo kluczowe nonlocal. Jak wspomniano w PEP, możesz użyć klasy, aby osiągnąć to samo (trochę bałagan):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
po której następuje ns.x = 3
. Nie jest ładny, ale dla mojego oka jest nieco mniej brzydki.
class Namespace: x = 3
?
ns
jest obiektem globalnym, dlatego możesz odwołać się ns.x
na poziomie modułu w print
instrukcji na samym końcu .
Istnieje inny sposób implementacji zmiennych nielokalnych w Pythonie 2, na wypadek gdyby którakolwiek z odpowiedzi tutaj była niepożądana z jakiegokolwiek powodu:
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
Używanie nazwy funkcji w instrukcji przypisania zmiennej jest zbędne, ale wydaje mi się to prostsze i czystsze niż umieszczenie zmiennej w słowniku. Wartość jest zapamiętywana z jednego wezwania do drugiego, tak jak w odpowiedzi Chrisa B.
f = outer()
a później to zrobisz g = outer()
, f
licznik zostanie zresetowany. To dlatego, że oboje mają wspólny pojedyncza outer.y
zmienna, a nie każdy posiada własną niezależną jeden. Chociaż ten kod wygląda bardziej estetycznie niż odpowiedź Chrisa B, jego sposób wydaje się być jedynym sposobem na emulację leksykalnego zakresu, jeśli chcesz wywołać outer
więcej niż jeden raz.
outer.y
nie obejmuje niczego lokalnego dla wywołania funkcji (instancji) outer()
, ale przypisuje atrybut obiektu funkcji, który jest powiązany z nazwą outer
w otaczającym ją zakresie. A zatem można by równie dobrze wykorzystane, na piśmie outer.y
, żadnego innego imienia, a nie outer
, pod warunkiem, że wiadomo, że jest związany w tym zakresie. Czy to jest poprawne?
outer.y
użyć nazwy inner.y
(ponieważ inner
jest ona związana wewnątrz wywołania outer()
, co jest dokładnie takim zakresem, jaki chcemy), ale umieszczając inicjalizacja inner.y = 0
po zdefiniowaniu inner (bo obiekt musi istnieć, gdy tworzony jest atrybut), ale oczywiście przed return inner
?
Oto coś zainspirowanego sugestią, którą Alois Mahdal poczynił w komentarzu dotyczącym innej odpowiedzi :
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)
Aktualizacja
Kiedy ostatnio spojrzałem na to wstecz, uderzyło mnie to, jak wygląda dekorator - kiedy dotarło do mnie, że wdrożenie go w taki sposób, aby było bardziej ogólne i użyteczne (chociaż prawdopodobnie w pewnym stopniu obniża to jego czytelność).
# 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)
Zauważ, że obie wersje działają zarówno w Pythonie 2, jak i 3.
W regułach określania zakresu Pythona jest brodawka - przypisanie powoduje, że zmienna jest lokalna dla jej bezpośrednio obejmującego zakres funkcji. W przypadku zmiennej globalnej można to rozwiązać za pomocą global
słowa kluczowego.
Rozwiązaniem jest wprowadzenie obiektu, który jest współdzielony między dwoma zakresami, który zawiera zmienne modyfikowalne, ale do niego odwołuje się zmienna, która nie jest przypisana.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Alternatywą jest hakowanie niektórych zakresów:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Być może będziesz w stanie wymyślić jakąś sztuczkę, aby uzyskać nazwę parametru outer
, a następnie przekazać ją jako nazwę zmiennej, ale bez polegania na nazwie, outer
której chciałbyś użyć kombinatora Y.
nonlocal
. locals()
tworzy słownik outer()
s locals w tym czasie inner()
jest zdefiniowany, ale zmiana tego słownika nie zmienia v
in outer()
. To już nie zadziała, gdy masz więcej funkcji wewnętrznych, które chcą udostępniać zmienną zamkniętą. Powiedz a, inc()
a dec()
zwiększaj i zmniejszaj wspólny licznik.
nonlocal
to funkcja Pythona 3.
nonlocal
w Pythonie 2 . Twoje pomysły nie obejmują przypadku ogólnego, ale tylko ten z jedną funkcją wewnętrzną. Spójrz na tę istotę jako przykład. Obie funkcje wewnętrzne mają swój własny kontener. Potrzebujesz zmiennego obiektu w zakresie funkcji zewnętrznej, jak sugerowały już inne odpowiedzi.
nonlocal
słowa kluczowego wprowadzonego w Pythonie 3.
Inny sposób, aby to zrobić (chociaż jest zbyt szczegółowy):
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
Rozszerzając eleganckie rozwiązanie Martineau powyżej o praktyczny i nieco mniej elegancki przypadek użycia otrzymuję:
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)
Użyj zmiennej globalnej
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
Osobiście nie lubię zmiennych globalnych. Ale moja propozycja jest oparta na odpowiedzi https://stackoverflow.com/a/19877437/1083704
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
gdzie użytkownik musi zadeklarować zmienną globalną ranks
, za każdym razem, gdy musisz wywołać metodę report
. Moje ulepszenie eliminuje potrzebę inicjowania zmiennych funkcji od użytkownika.
inner
, ale nie możesz jej przypisywać, ale możesz modyfikować jej klucze i wartości. Pozwala to uniknąć używania zmiennych globalnych.