Termin "zamknięcie" został zapożyczony z programowania funkcyjnego i pojawił się w Pythonie od samego początku. Zamknięcia pozwalają funkcjom zapamiętywać otoczenie, w którym zostały stworzone, nawet jeśli są wywoływane poza tym otoczeniem. Ta koncepcja zapewnia elastyczność i pozwala na realizację wielu wzorców, w tym fabryk funkcji i leniwych obliczeń.
W Pythonie funkcje są obiektami pierwszej klasy. Czasami zachodzi potrzeba, aby funkcja zagnieżdżona korzystała ze zmiennych z zakresu funkcji zewnętrznej, nawet po jej zakończeniu. Zwykły leksykalny zasięg tego nie gwarantuje przy zwracaniu funkcji. Jeśli taka funkcja odwołuje się do zmiennych otoczenia swojego utworzenia — powstaje zamknięcie.
Zamknięcie powstaje, jeśli wewnętrzna funkcja odnosi się do zmiennych zdefiniowanych w zewnętrznej, a ta zewnętrzna zwraca wewnętrzną na zewnątrz. Jest to często wykorzystywane do tworzenia fabryk funkcji, enkapsulacji stanu bez klas oraz konstruowania funkcji z parametrami "na miejscu".
def make_multiplier(factor): def multiplier(x): return x * factor return multiplier mul2 = make_multiplier(2) mul3 = make_multiplier(3) print(mul2(10)) # 20 print(mul3(10)) # 30
nonlocal.Czy zamknięcie może zachować zmienny stan między wywołaniami, jeśli zmienna jest zmieniana wewnątrz funkcji zagnieżdżonej?
Tak, jeśli wewnętrzna funkcja skorzysta ze słowa kluczowego nonlocal. Bez nonlocal przypisanie tworzy nową lokalną zmienną, nie zmieniając zewnętrznej.
def counter(): count = 0 def inc(): nonlocal count count += 1 return count return inc c = counter() print(c()) # 1 print(c()) # 2
Czy można zrealizować prywatne zmienne w Pythonie, używając zamknięć, zamiast klas?
Tak, zamknięcie oferuje prostą implementację "prywatnych" zmiennych, niedostępnych z zewnątrz, jeśli nie są dostarczane gettery/settery wewnętrznej funkcji.
Czy zamknięcia stosowane są tylko do funkcji? Czy można zorganizować zamknięcie z lambdami w Pythonie?
Tak, zamknięcie może powstawać także z wyrażeń lambda, ponieważ są one podobne do def pod względem związania zmiennych leksykalnych.
def make_power(n): return lambda x: x ** n square = make_power(2) cube = make_power(3) print(square(4)) # 16 print(cube(2)) # 8
nonlocal.Fabryka funkcji, która formuje obsługiwacze w pętli, używa zmiennej pętli wewnątrz zamknięcia:
handlers = [] for i in range(3): def handler(x): return x + i handlers.append(handler) print([h(10) for h in handlers]) # [12, 12, 12]
Zalety:
Wady:
i, jej ostatnia wartość to 2 — zachowanie nieoczekiwane dla większości.Użyto argumentu domyślnego, aby "zablokować" wartość:
handlers = [] for i in range(3): def handler(x, j=i): return x + j handlers.append(handler) print([h(10) for h in handlers]) # [10, 11, 12]
Zalety:
Wady: