2.16. Zakres¶
Gdy Python wyszukuje nazwę wewnątrz funkcji, przeszukuje określoną sekwencję zakresów. Zrozumienie tej sekwencji wyjaśnia, dlaczego niektóre przypisania przesłaniają nazwy zewnętrzne, dlaczego inne je modyfikują i dlaczego funkcje zagnieżdżone mogą pamiętać wartości z miejsca, w którym zostały zdefiniowane.
Wyszukiwanie nazwy zaczyna się w lokalnym zakresie funkcji i przebiega na zewnątrz do zakresów modułu i wbudowanego, aż do znalezienia dopasowania.¶
2.16.1. Zakres lokalny i modułu¶
Nazwy zdefiniowane wewnątrz funkcji są lokalne dla tej funkcji i znikają, gdy wywołanie się kończy:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Nazwy zdefiniowane na najwyższym poziomie pliku .py mają zasięg modułu (czasami nazywany globalnym) i są widoczne wszędzie w tym pliku, także wewnątrz funkcji:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Odczyt nazwy na poziomie modułu z wnętrza funkcji jest automatyczny. Przypisanie do tej nazwy z wnętrza funkcji już nie – przypisanie tworzy nową zmienną lokalną, która przesłania nazwę na poziomie modułu przez resztę wywołania:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
Lewostronne counter czyni counter nazwą lokalną w bump, więc odczyt po prawej stronie nie ma wartości do znalezienia.
2.16.1.1. Słowo kluczowe global¶
Aby faktycznie ponownie przypisać nazwę na poziomie modułu z wnętrza funkcji, zadeklaruj ją najpierw jako global:
counter = 0
def bump():
global counter
counter += 1
Sięgaj po global oszczędnie. O funkcjach, które modyfikują ukryty stan, trudniej wnioskować niż o funkcjach, które przyjmują wartości jako argumenty i zwracają nowe wartości. Zwykłym rozwiązaniem problemu „muszę współdzielić stan” jest przekazanie obiektu (listy, słownika, instancji klasy) jako argumentu i modyfikowanie go zamiast tego.
2.16.2. Wyrażenia lambda¶
lambda tworzy małą anonimową funkcję w pojedynczym wyrażeniu:
square = lambda x: x * x
square(7) # 49
Jest dokładnie równoważne
def square(x):
return x * x
Ciało lambda musi być pojedynczym wyrażeniem – bez instrukcji, bez wielu wierszy. Głównym zastosowaniem jest przekazanie drobnej funkcji jako argumentu do czegoś, co przyjmuje funkcję:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Gdy ciało rozrośnie się poza jedno wyrażenie, przełącz się na prawdziwe def. Nazwanie funkcji za pomocą def daje jej także nazwę w śladach stosu, której lambda nie ma.
2.16.3. Domknięcia¶
Funkcja zdefiniowana wewnątrz innej funkcji może odczytywać nazwy z zakresu funkcji otaczającej. Funkcja wewnętrzna przechwytuje te nazwy i działa nadal nawet po zakończeniu wywołania zewnętrznego:
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))
Wyjście:
105 110
add5 i add10 to dwie osobne funkcje, każda pamiętająca własne n. Funkcję, która w ten sposób tworzy i zwraca dostosowaną funkcję wewnętrzną, nazywa się domknięciem. To główny powód, dla którego język w ogóle potrzebuje funkcji zagnieżdżonych – sposób na wbudowanie pewnego stanu w wartość funkcji, a następnie przekazanie tej wartości jako pojedynczego obiektu wywoływalnego.
Odczyt przechwyconych nazw odbywa się automatycznie. Ponowne związanie jednej z nich wymaga dodatkowego słowa kluczowego. Poniższy przykład robi to, co wygląda poprawnie, i zawodzi:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
Przypisanie do count wewnątrz tick czyni count lokalnym dla tick, tak samo jak uczyniłoby je lokalnym w funkcji najwyższego poziomu. Słowo kluczowe nonlocal mówi Pythonowi: „ta nazwa żyje w funkcji otaczającej, zwiąż ją ponownie tam”:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Wyjście:
1 2 3
nonlocal jest dla zakresu funkcji otaczającej tym, czym global dla zakresu modułu. Zauważ, że modyfikowanie przechwyconego obiektu (wywołanie some_list.append(...), some_dict[k] = v) nie wymaga nonlocal – nazwa nie jest ponownie wiązana, zmieniany jest tylko obiekt, na który wskazuje.