Bevezetés a zárásokba Pythonban

Annak megértése, hogy mit, mikor és miért kell használni a zárakat!

A lezárások elegáns Python-konstrukciók. Ebben a cikkben megtudjuk, hogyan kell meghatározni a lezárást, miért és mikor kell használni őket.

Mielőtt azonban rátérnénk arra, hogy mi a lezárás, először meg kell értenünk, mi az a beágyazott függvény, és hogyan működnek a hatókör-szabályok számukra. Tehát kezdjük.

Hatásköri szabályok és beágyazott függvények a Pythonban

Amikor egy függvény végrehajtódik, egy új helyi névtér jön létre, amely azt a helyi környezetet képviseli, amely a függvénytörzsben hozzárendelt függvényparaméterek és változók neveit tartalmazza. A névteret szótárnak tekinthetjük, amelyben a kulcsok az objektumok nevei, az értékek pedig maguk az objektumok.

A nevek feloldásakor az értelmező először a helyi névtérben keres. Ha nincs egyezés, akkor a globális névtérben keres, amely az a modul, amelyben a függvény definiálva van. Ha továbbra sem talál egyezést, akkor végül ellenőrzi a beépített névteret, mielőtt felhozná a NameError kivételt. Ezt az alábbi ábra szemlélteti:

Tekintsük az alábbi példát:

age = 27
def birthday(): 
  age = 28
birthday()
print(age)  # age will still be 27
>>
27

Amikor változókat rendelünk hozzá egy függvényen belül, mindig a függvény helyi névteréhez vannak kötve; ennek eredményeként a függvénytörzsben lévő age változó egy teljesen új, 28-as értéket tartalmazó objektumra vonatkozik, nem a külső változóra. Ez a viselkedés a global utasítással módosítható. Az alábbi példa kiemeli, hogy:

age = 27
name = "Sarah"
def birthday(): 
  global age       # 'age' is in global namespace 
  age = 28
  name = "Roark"
birthday()         # age is now 28. name will still be "Sarah".

A Python támogatja a beágyazott függvénydefiníciókat is (függvényen belüli függvény). Íme egy példa:

def countdown(start):
  # This is the outer enclosing function
  def display():
    # This is the nested function
    n = start
    while n > 0:
      n-=1
      print('T-minus %d' % n)
 
  display()
# We execute the function
countdown(3)
>>>
T-minus 3
T-minus 2
T-minus 1

Zárási függvény meghatározása

A fenti példában mi történne, ha a countdown() függvény utolsó sora a display függvényt adja vissza, ahelyett, hogy meghívná? Ez azt jelenti, hogy a függvényt a következőképpen határozták meg:

def countdown(start):
  # This is the outer enclosing function
  def display():
    # This is the nested function
    n = start
    while n > 0:
      n-=1
      print('T-minus %d' % n)
  return display
# Now let's try calling this function.
counter1 = countdown(2)
counter1()
>>>
T-minus 2
T-minus 1

A countdown() függvény 2 értékkel lett meghívva, a visszaadott függvény pedig a counter1 névhez volt kötve. a counter1() végrehajtásakor a start értékét használja, amelyet eredetileg a countdown() kapott. Így a counter1() hívásakor az érték még mindig emlékezett, bár már befejeztük a countdown() függvény végrehajtását.

Ezt a technikát, amellyel bizonyos adatokat (ebben az esetben 2) csatolnak a kódhoz, zárásnak nevezik a Pythonban.

Ezt az értéket a befoglaló hatókörben akkor is megjegyzi, ha a változó kikerül a hatókörből, vagy magát a függvényt eltávolítják az aktuális névtérből. Megpróbálhatjuk a következő kódot ennek megerősítésére:

>>> del countdown
>>> counter1()
T-minus 2
T-minus 1
>>> countdown(2)
Traceback (most recent call last):
...
NameError: name 'countdown' is not defined

Mikor használjunk zárakat?

Ha kevés metódust (a legtöbb esetben egy módszert) kell megvalósítani egy osztályban, a lezárások alternatív és elegánsabb megoldást jelenthetnek. Ezenkívül a bezárások és a beágyazott függvények különösen hasznosak, ha a lusta vagy késleltetett kiértékelés koncepciója alapján szeretnénk kódot írni. Íme egy példa:

from urllib.request import urlopen
def page(url): 
  def get(): 
    return urlopen(url).read() 
  return get

A fenti példában a page() függvény valójában nem végez semmilyen számítást. Ehelyett csupán létrehoz és visszaad egy get() függvényt, amely lekéri a weboldal tartalmát, amikor meghívják. Így a get()-ben végrehajtott számítás ténylegesen késik a program valamely későbbi pontjáig, amikor a get()értéke megtörténik. Például:

>>> url1 = page("http://www.google.com") 
>>> url2 = page("http://www.bing.com") 
>>> url1
<function page.<locals>.get at 0x10a6054d0>
>>> url2
<function page.<locals>.get at 0x10a6055f0>
  
>>> gdata = url1()     # Fetches http://www.google.com 
>>> bdata = url2()     # Fetches http://www.bing.com
>>>

A zárófüggvénybe zárt értékeket meg lehet találni.

Minden függvényobjektumnak van egy __closure__ attribútuma, amely egy sor cellaobjektumot ad vissza, ha ez egy zárófüggvény. A fenti példára hivatkozva tudjuk, hogy a url1 és url2 zárófüggvények.

>>> page.__closure__       # Returns None since not a closure
>>> url1.__closure__
(<cell at 0x10a5f1250: str object at 0x10a5f3120>,)

A cellaobjektum cell_contents attribútuma a zárt értéket tárolja.

>>> url1.__closure__[0].cell_contents
'http://www.google.com'
>>> url2.__closure__[0].cell_contents
'http://www.bing.com'

Következtetések:

A Python bezárása akkor határozható meg, ha egy beágyazott függvény egy értékre hivatkozik a befoglaló hatókörében. A lezárások az adatok elrejtésének valamilyen formáját biztosítják. A lezárás rendkívül hatékony módja lehet az állapot megőrzésének egy sor függvényhívás során. Bezárási függvény létrehozása Pythonban:

  • Kell egy beágyazott függvényünk.
  • A beágyazott függvénynek a befoglaló függvényben meghatározott értékre kell hivatkoznia.
  • A befoglaló függvénynek vissza kell adnia a beágyazott függvényt.