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.

Nested boxes showing a local scope inside a module scope inside the built-in scope, with an arrow indicating that name lookup walks outward from the innermost frame.

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.