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.

Zagnieżdżone ramki pokazujące zakres lokalny wewnątrz zakresu modułu wewnątrz zakresu wbudowanego, ze strzałką wskazującą, że wyszukiwanie nazwy przebiega na zewnątrz od najbardziej wewnętrznej ramki.

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.