Zamknięcia (closures) — potężny mechanizm Pythona, pozwalający na tworzenie funkcji z "zapamiętanymi" danymi z otaczającego kontekstu.
W językach funkcyjnych oraz w Pythonie funkcje są obiektami pierwszorzędnymi. Funkcja może zwrócić nową funkcję, która 'zamknie' zmienne ze swojej zewnętrznej przestrzeni widzenia.
Programiści często mylą się, jak dokładnie zamknięte zmienne "żyją" wewnątrz zamknięcia, gdy są odczytywane lub zapisywane, oraz jak bezpiecznie z nimi pracować (szczególnie z obiektami mutowalnymi).
Jeżeli wewnętrzna funkcja korzysta z zmiennych zewnętrznej funkcji, są one automatycznie "zapamiętywane" — nawet jeśli zewnętrzna funkcja już zakończyła działanie. Dla odczytu zmiennej wszystko jest oczywiste, ale jeśli musisz zmienić wartość — należy użyć słowa kluczowego nonlocal. Przy pracy z obiektami mutowalnymi (listy, słowniki) to szczególna strefa ryzyka.
Przykład:
def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2
Kluczowe cechy:
nonlocal (inaczej operacja tworzy lokalną zmienną).Czy można zmieniać wartość zamkniętej zmiennej wewnątrz funkcji bez nonlocal?
Nie. Jeśli spróbujesz przypisać nową wartość, nie używając nonlocal, Python traktuje to jako tworzenie nowej lokalnej zmiennej, a stara wartość nie wyjdzie na zewnątrz.
Przykład:
def make_counter(): count = 0 def inner(): count += 1 # Błąd UnboundLocalError! return count return inner
Czy można przekazywać argumenty do zewnętrznego scope przez zamknięcie?
Tak, zamknięcie "zapamięta" wszelkie zmienne dostępne w zewnętrznej przestrzeni widzenia, w tym atrybuty klas, zmienne globalne itp. Ale zmiana tych zmiennych wymaga szczególnych wysiłków (np. użycia nonlocal lub global).
Co się dzieje z obiektami mutowalnymi wewnątrz zamknięcia?
Jeśli zamknięta zmienna jest referencją do obiektu mutowalnego, na przykład listy, możesz zmodyfikować jego zawartość bez nonlocal, ale jeśli spróbujesz przypisać zmienną — będzie potrzebne nonlocal.
Przykład:
def make_appender(): result = [] def append(x): result.append(x) # Można! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]
Programista pisze zamknięcie, zmienia zmienną bez nonlocal — pojawia się UnboundLocalError.
Zalety:
Wady:
Wyraźne użycie nonlocal dla kontrolowanego stanu w zamknięciu.
Zalety:
Wady: