Python - Zatvaranje (Closures)


Ne-lokalna varijabla u ugniježdenoj funkciji

Prije nego što uđemo u to što je zatvaranje (closure), prvo moramo shvatiti što je ugniježdena funkcija i nelokalna varijabla. Funkcija definisana unutar druge funkcije naziva se ugniježdena funkcija. Ugniježdene funkcije mogu pristupiti varijablama obuhvaćenog opsega. U Pythonu su ove ne-lokalne varijable prema zadanim postavkama samo za čitanje i moramo ih eksplicitno deklarisati kao ne-lokalne (koristeći nonlocal ključnu riječ) kako bismo ih modifikovali. Slijedi primjer ugniježdene funkcije koja pristupa ne-lokalnoj varijabli.

def print_msg(msg):
    # Ovo je vanjska funkcija zatvaranja

    def printer():
        # Ovo je ugniježdena funkcija
        print(msg)

    printer()

# Izvršavanje funkcije
# Ispisuje: Hello
print_msg("Hello")

Vidimo da je ugniježdena funkcija printer() bila u mogućnosti pristupiti ne-lokalnoj varijabli msg funkcije koja uključuje.



Definisanje funkcije zatvaranja

U prethodnom primjeru, šta bi se dogodilo da posljednji red funkcije print_msg() vrati funkciju printer() umjesto da je pozove? To znači da je funkcija definisana kao što je prikazano u nastavku:

def print_msg(msg):
    # Ovo je vanjska funkcija zatvaranja

    def printer():
        # Ovo je ugniježdena funkcija
        print(msg)

    return printer  # vraća ugniježdenu funkciju


# Pokušajmo sada pozvati ovu funkciju.
# Ispisuje: Hello
another = print_msg("Hello")
another()

Funkcija print_msg() pozvana je stringom "Hello", a vraćena funkcija vezana je za drugo ime. Po pozivu funkcije another(), poruka se i dalje pamtila, iako smo već završili s izvršavanjem funkcije print_msg(). Ova je tehnika pomoću koje se neki podaci ("Hello u ovom slučaju") kači na kod naziva se zatvaranje u Pythonu. Ova vrijednost u opsegu koji se zatvara pamti se čak i kada varijabla izlazi iz opsega ili se sama funkcija ukloni iz trenutnog prostora imena. Pokušajte pokrenuti sljedeći primjer u Python shell da vidite šta će biti izlaz.

>>> del print_msg
>>> another()
Hello
>>> print_msg("Hello")
Traceback (most recent call last):
...
NameError: name 'print_msg' is not defined

Ovdje vraćena funkcija i dalje radi čak i kad je izvorna funkcija izbrisana.



Kada imamo zatvaranja (closures)?

Kao što se vidi iz gornjeg primjera, imamo zatvaranje u Pythonu kada ugniježdena funkcija upućuje na vrijednost u svom opsegu zatvaranja. Kriteriji koji moraju biti ispunjeni da bi se stvorilo zatvaranje u Pythonu sažeti su u slijedećim tačkama.

  • Moramo imati ugniježdenu funkciju (funkcija unutar funkcije).
  • Ugniježdena funkcija mora se odnositi na vrijednost definisanu u priloženoj funkciji.
  • Funkcija zatvaranja mora vratiti ugniježdenu funkciju.


Kada koristiti zatvaranje (closures)?

Pa čemu služe zatvaranja? Zatvaranja mogu izbjeći upotrebu globalnih vrijednosti i pružaju neki oblik skrivanja podataka. Takođe može pružiti objektno orijentirano rješenje problema. Kada postoji nekoliko metoda (u većini slučajeva jedna metoda) koje treba implementovati u klasi, zatvaranja mogu pružiti alternativno i elegantnije rješenje. Ali kada se broj atributa i metoda poveća, bolje je implementovati klasu. Evo jednostavnog primjera gdje bi zatvaranje moglo biti poželjnije od definisanja klase i izrade objekata. Ali izbor je na vama .

def make_multiplier_of(n):
    def multiplier(x):
        return x * n
    return multiplier


# Množitelj sa 3
times3 = make_multiplier_of(3)

# Množitelj sa 5
times5 = make_multiplier_of(5)

# Ispisuje: 27
print(times3(9))

# Ispisuje: 15
print(times5(3))

# Ispisuje: 30
print(times5(times3(2)))

Python Dekoratori u velikoj mjeri koriste i zatvarače (closures). Na kraju, dobro je naglasiti da se vrijednosti koje se zatvaraju u funkciji zatvaranja mogu naći. Svi funkcijski objekti imaju atribut __closure__ koji vraća skup ćelijskih objekata ako je funkcija zatvaranja (closures). Pozivajući se na gornji primjer, znamo da su times3 i times5 funkcije zatvaranja (closures).

>>> make_multiplier_of.__closure__
>>> times3.__closure__
(<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)

Objekt ćelije ima atribut cell_contents koji čuva zatvorenu vrijednost.

>>> times3.__closure__[0].cell_contents
3
>>> times5.__closure__[0].cell_contents
5