Closure (domknięcie) to funkcja, która „zapamiętuje” zmienne z zakresu zewnętrznego, nawet po zakończeniu wykonywania funkcji zewnętrznej.
Kluczowe elementy:
- Funkcja zagnieżdżona (wewnątrz innej funkcji)
- Funkcja wewnętrzna odwołuje się do zmiennych zewnętrznych
- Funkcja zewnętrzna zwraca funkcję wewnętrzną
Dlaczego closures są ważne?
- Enkapsulacja danych
- Tworzenie funkcji fabryk
- Dekoratory
- Callbacks i event handlery
Podstawy – funkcje zagnieżdżone
def zewnetrzna():
"""Funkcja zewnętrzna"""
wiadomosc = "Hello"
def wewnetrzna():
"""Funkcja wewnętrzna"""
print(wiadomosc) # Dostęp do zmiennej zewnętrznej
wewnetrzna()
zewnetrzna() # Hello
Zwracanie funkcji wewnętrznej
def zewnetrzna():
wiadomosc = "Hello"
def wewnetrzna():
print(wiadomosc)
return wewnetrzna # Zwracamy funkcję (bez nawiasów!)
# Przypisz funkcję do zmiennej
funkcja = zewnetrzna()
# Wywołaj
funkcja() # Hello
Ważne: Funkcja wewnetrzna „zapamiętała” zmienną wiadomosc mimo że zewnetrzna() już się zakończyła!
Pierwszy prawdziwy closure
def stworz_powitanie(pozdrowienie):
"""Funkcja fabrykująca - tworzy spersonalizowane powitania"""
def powitaj(imie):
return f"{pozdrowienie}, {imie}!"
return powitaj
# Stwórz różne funkcje powitań
hello = stworz_powitanie("Hello")
czesc = stworz_powitanie("Cześć")
hola = stworz_powitanie("Hola")
# Użycie
print(hello("Anna")) # Hello, Anna!
print(czesc("Jan")) # Cześć, Jan!
print(hola("Maria")) # Hola, Maria!
Co się dzieje:
stworz_powitanie("Hello")tworzy funkcjępowitajktóra „pamięta”pozdrowienie = "Hello"- Każde wywołanie tworzy niezależne closure z własną wartością
Closure z wieloma zmiennymi
def licznik(start=0):
"""Tworzy licznik zaczynający od start"""
count = start
def increment():
nonlocal count # Pozwala modyfikować zmienną zewnętrzną
count += 1
return count
return increment
# Stwórz liczniki
licznik1 = licznik(0)
licznik2 = licznik(100)
print(licznik1()) # 1
print(licznik1()) # 2
print(licznik1()) # 3
print(licznik2()) # 101
print(licznik2()) # 102
Kluczowe słowo nonlocal:
- Bez
nonlocal– Python tworzy nową lokalną zmienną - Z
nonlocal– modyfikuje zmienną z zakresu zewnętrznego
Przykład: Mnożnik
def stworz_mnoznik(n):
"""Tworzy funkcję mnożącą przez n"""
def mnoz(x):
return x * n
return mnoz
# Stwórz różne mnożniki
razy_2 = stworz_mnoznik(2)
razy_5 = stworz_mnoznik(5)
razy_10 = stworz_mnoznik(10)
print(razy_2(10)) # 20
print(razy_5(10)) # 50
print(razy_10(10)) # 100
Przykład: Funkcja potęgująca
def stworz_potege(wykladnik):
"""Tworzy funkcję podnoszącą do potęgi"""
def poteguj(podstawa):
return podstawa ** wykladnik
return poteguj
# Różne potęgi
kwadrat = stworz_potege(2)
szescian = stworz_potege(3)
czwarta = stworz_potege(4)
print(kwadrat(5)) # 25
print(szescian(5)) # 125
print(czwarta(2)) # 16
Closure ze stanem
def stworz_konto(wlasciciel, saldo_poczatkowe=0):
"""Tworzy konto bankowe z enkapsulacją"""
saldo = saldo_poczatkowe
def wplata(kwota):
nonlocal saldo
if kwota > 0:
saldo += kwota
return f"{wlasciciel}: Wpłata {kwota}. Saldo: {saldo}"
return "Kwota musi być dodatnia"
def wyplata(kwota):
nonlocal saldo
if kwota > saldo:
return f"{wlasciciel}: Brak środków. Saldo: {saldo}"
if kwota > 0:
saldo -= kwota
return f"{wlasciciel}: Wypłata {kwota}. Saldo: {saldo}"
return "Kwota musi być dodatnia"
def sprawdz_saldo():
return f"{wlasciciel}: Saldo: {saldo}"
# Zwróć słownik funkcji
return {
'wplata': wplata,
'wyplata': wyplata,
'saldo': sprawdz_saldo
}
# Stwórz konta
konto_jan = stworz_konto("Jan", 1000)
konto_anna = stworz_konto("Anna", 500)
# Użycie
print(konto_jan['saldo']()) # Jan: Saldo: 1000
print(konto_jan['wplata'](500)) # Jan: Wpłata 500. Saldo: 1500
print(konto_jan['wyplata'](200)) # Jan: Wypłata 200. Saldo: 1300
print(konto_anna['saldo']()) # Anna: Saldo: 500
Zalety:
- Dane są prywatne (nie można bezpośrednio modyfikować
saldo) - Każde konto ma niezależny stan
- Enkapsulacja bez klas
Closure vs Klasy
Z klasą:
class Licznik:
def __init__(self, start=0):
self.count = start
def increment(self):
self.count += 1
return self.count
licznik = Licznik(0)
print(licznik.increment()) # 1
Z closure:
def licznik(start=0):
count = start
def increment():
nonlocal count
count += 1
return count
return increment
licznik = licznik(0)
print(licznik()) # 1
Kiedy używać:
- Closure – proste przypadki, enkapsulacja jednej funkcji
- Klasy – złożone obiekty, wiele metod, dziedziczenie
Praktyczne zastosowania
1. Dekoratory (najczęstsze!)
def mierz_czas(funkcja):
"""Dekorator mierzący czas wykonania"""
import time
def wrapper(*args, **kwargs):
start = time.time()
wynik = funkcja(*args, **kwargs)
koniec = time.time()
print(f"{funkcja.__name__} wykonała się w {koniec - start:.4f}s")
return wynik
return wrapper
@mierz_czas
def wolna_funkcja():
import time
time.sleep(1)
return "Gotowe"
wynik = wolna_funkcja()
# wolna_funkcja wykonała się w 1.0012s
2. Funkcje fabrykujące
def stworz_validator(min_dlugosc, max_dlugosc):
"""Tworzy validator długości"""
def waliduj(tekst):
dlugosc = len(tekst)
if dlugosc < min_dlugosc:
return False, f"Za krótkie (min: {min_dlugosc})"
if dlugosc > max_dlugosc:
return False, f"Za długie (max: {max_dlugosc})"
return True, "OK"
return waliduj
# Stwórz różne validatory
waliduj_haslo = stworz_validator(8, 20)
waliduj_nick = stworz_validator(3, 15)
print(waliduj_haslo("abc")) # (False, 'Za krótkie (min: 8)')
print(waliduj_haslo("Python123")) # (True, 'OK')
print(waliduj_nick("Jo")) # (False, 'Za krótkie (min: 3)')
3. Callbacks z kontekstem
def stworz_button_handler(button_id):
"""Tworzy handler dla przycisku"""
klikniecia = 0
def on_click():
nonlocal klikniecia
klikniecia += 1
print(f"Przycisk {button_id} kliknięty {klikniecia} razy")
return on_click
# Stwórz handlery
button1_click = stworz_button_handler("Zapisz")
button2_click = stworz_button_handler("Anuluj")
# Symulacja kliknięć
button1_click() # Przycisk Zapisz kliknięty 1 razy
button1_click() # Przycisk Zapisz kliknięty 2 razy
button2_click() # Przycisk Anuluj kliknięty 1 razy
4. Memoizacja (cache wyników)
def memoize(funkcja):
"""Closure cache'ujące wyniki funkcji"""
cache = {}
def wrapper(*args):
if args in cache:
print(f"Zwracam z cache dla {args}")
return cache[args]
print(f"Obliczam dla {args}")
wynik = funkcja(*args)
cache[args] = wynik
return wynik
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(5))
# Obliczam dla (5,)
# Obliczam dla (4,)
# Obliczam dla (3,)
# Obliczam dla (2,)
# Obliczam dla (1,)
# Obliczam dla (0,)
# Zwracam z cache dla (1,)
# Zwracam z cache dla (2,)
# Zwracam z cache dla (3,)
# 5
5. Rate limiting
def rate_limiter(max_calls, time_window):
"""Ogranicza liczbę wywołań funkcji w czasie"""
import time
calls = []
def decorator(funkcja):
def wrapper(*args, **kwargs):
nonlocal calls
teraz = time.time()
# Usuń stare wywołania
calls = [c for c in calls if teraz - c < time_window]
if len(calls) >= max_calls:
return f"Za dużo wywołań! Max {max_calls} w {time_window}s"
calls.append(teraz)
return funkcja(*args, **kwargs)
return wrapper
return decorator
@rate_limiter(max_calls=3, time_window=10)
def wyslij_email(adres):
return f"Email wysłany do {adres}"
# Pierwsze 3 wywołania - OK
print(wyslij_email("jan@test.com"))
print(wyslij_email("anna@test.com"))
print(wyslij_email("piotr@test.com"))
# 4-te - za dużo
print(wyslij_email("ewa@test.com"))
# Za dużo wywołań! Max 3 w 10s
Inspekcja closure
def zewnetrzna(x):
def wewnetrzna(y):
return x + y
return wewnetrzna
closure_func = zewnetrzna(10)
# Sprawdź co closure "zapamiętała"
print(closure_func.__closure__)
# (<cell at 0x...: int object at 0x...>,)
# Wartość
print(closure_func.__closure__[0].cell_contents) # 10
Podsumowanie Closures
Definicja:
- Funkcja zagnieżdżona
- Używa zmiennych z funkcji zewnętrznej
- Jest zwracana przez funkcję zewnętrzną
Kluczowe słowa:
nonlocal– modyfikuj zmienną zewnętrzną
Zastosowania:
- Dekoratory
- Funkcje fabrykujące
- Enkapsulacja danych
- Callbacks z kontekstem
- Memoizacja
Zalety:
- Prywatne dane
- Prostsza składnia niż klasy
- Funkcyjne programowanie
Ćwiczenia
- Napisz closure zwracające funkcję mnożącą przez dowolną liczbę.
- Napisz closure tworzące licznik (increment, decrement, reset).
- Napisz closure zapisujące historię wywołań funkcji.
- Stwórz funkcję fabrykującą validatory (min długość, max długość).
- Napisz closure implementujące prostą cache (memoizację).
- Stwórz closure z prywatnym stanem (np. konto bankowe).
- Napisz closure logujące wywołania funkcji.
- Stwórz closure obliczające średnią ruchomą.
- Napisz closure rate limitera (max wywołań w czasie).
- Stwórz closure generujące unikalne ID.