A válasz igen. Már csak az a kérdés, hogy hogyan. Ez azért merül fel, mert amikor closure-ről van szó, általában egy körülzáró függvénybe ágyazott egyetlen függvényre gondolunk, ami a körülzáró függvény visszatérési értéke lesz, és ami később a hívása helyén hozzáfér a körülzáró függvény lokális változóihoz.
Azonban semmi nem korlátozza azt, hogy ne egy, hanem több beágyazott függvényt definiáljunk a körülzáró függvényen belül. Ilyen esetben a körülzáró függvény visszatérési értéke nem egyetlen függvény lesz, hanem a beágyazott függvények sorozata, ami a hívó program számára egy tuple konténerben való átadást jelent.
Ahhoz, hogy világosabb legyen, hogy miről is van szó, vegyünk egy ezt szemléltető példát! Tegyük fel, hogy egy bank számlanyilvántartását és kezelését akarjuk modellezni. Ennek keretében meg kell tudni nyitni egy számlát adott egyedi számlaszámmal, valamint be kell tudni tenni a számlára adott összeget, ki kell tudni venni róla összeget, ha az egyenleg engedi, és le is kell tudni kérni az egyenleget. Ez tehát négy művelet megvalósítását igényli: nyitás, betét, kivét és egyenleg lekérdezés.
A feladatot – mint általában – több módon is meg lehet oldani. Legtöbbször ilyenkor egy osztályt definiálnak, amelynek metódusai lesznek a nyitás, betét, kivét és egyenleg lekérdezés műveletek, a bankszámla adatokat (számlaszám és aktuális egyenleg) pedig egy megfelelő konténerre hivatkozó adatattribútum tárolja.
Mi most nem ezt tesszük, hanem a műveleteket egyetlen körülzáró függvénybe ágyazott closure függvényekkel valósítjuk meg, amelyek – mivel ugyanabban a körülzáró függvényben vannak definiálva – hozzáférnek a körülzáró függvény lokális változójaként létrehozott szótárhoz, amely a számlaszám-egyenleg értékpárokat tartalmazza.
A függvénydefiníciót és a használatot alább láthatjuk két módon is. Az első esetben a négy closure függvényt egymástól független külön változókhoz rendeljük. A másik változatban, mivel a függvények logikailag összetartoznak, egy mezőneves tuple attribútumaiként valósítjuk meg. A teszteredmények ugyanarra a bemenő adatokra természetesen mindkét esetben ugyanazok.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
from collections import namedtuple def bankszámlakezelés(): # Ez a szótár tárolja a számlaszám-egyenleg párokat. # A szótár tartalmát minden beágyazott függvény tudja olvasni és írni. számlák = {} def nyitás(számlaszám): """Létrehoz egy számlát az adott számlaszámmal.""" számlák.setdefault(számlaszám, 0) def betét(számlaszám, összeg): """A megadott összeggel növeli az adott számla egyenlegét.""" if számlaszám in számlák: számlák[számlaszám] += összeg return összeg return 0 def kivét(számlaszám, összeg): """A megadott összeggel csökkenti az adott számla egyenlegét.""" if számlaszám in számlák: if (egyenleg:=számlák.get(számlaszám)) >= összeg: számlák[számlaszám] = egyenleg - összeg return összeg return 0 def egyenleg(számlaszám): """Visszaadja az adott számla egyenlegét.""" if számlaszám in számlák: return számlák.get(számlaszám) return 0 # A visszatérési érték a beágyazott függvényobjektumok tuple konténere. return nyitás, betét, kivét, egyenleg # TESZT # Számlakezelő egyedi closure függvények. számla_nyitás, számlára_betét, számláról_kivét, számla_egyenleg = bankszámlakezelés() # Számlakezelő függvények mezőneves tuple attribútumaiként. Bank = namedtuple('Bank', 'számla_nyitás számlára_betét számláról_kivét számla_egyenleg') bank = Bank(*bankszámlakezelés()) # Megnyitandó számlák számlaszámai és kezdeti betétek. számlaszámok = (111, 222) betétek = (10000, 250000) # Számlák kezelése az egyedi closure függvényekkel. for számlaszám, betét in zip(számlaszámok, betétek): számla_nyitás(számlaszám) print(f'Betét = {számlára_betét(számlaszám, betét)}') print(f'Számlaszám: {számlaszám}, egyenlege = {számla_egyenleg(számlaszám)}') print(f'Kivét = {számláról_kivét(számlaszám, 50000)}') print(f'Számlaszám: {számlaszám}, egyenlege = {számla_egyenleg(számlaszám)}') # Számlák kezelése mezőneves tuple konténer attribútumaival. for számlaszám, betét in zip(számlaszámok, betétek): bank.számla_nyitás(számlaszám) print(f'Betét = {bank.számlára_betét(számlaszám, betét)}') print(f'Számlaszám: {számlaszám}, egyenlege = {bank.számla_egyenleg(számlaszám)}') print(f'Kivét = {bank.számláról_kivét(számlaszám, 50000)}') print(f'Számlaszám: {számlaszám}, egyenlege = {bank.számla_egyenleg(számlaszám)}') # Eredmény mindkét esetben: # Betét = 10000 # Számlaszám: 111, egyenlege = 10000 # Kivét = 0 # Számlaszám: 111, egyenlege = 10000 # Betét = 250000 # Számlaszám: 222, egyenlege = 250000 # Kivét = 50000 # Számlaszám: 222, egyenlege = 200000 |
Vegyük észre, hogy a számlaadatokat tartalmazó szótár ebben a megoldásban teljesen biztonságban van, még véletlenül sem lehet felülírni kívülről, ami viszont elvileg nem lenne igaz, ha az egyedi osztály definíción alapuló megközelítést alkalmaztunk volna, mert mint bizonyára tudjuk, Pythonban a privátnak minősített változókhoz is hozzá lehet férni, ha ismerjük a nevüket.
Az ilyen jellegű closure megoldások mégsem annyira elterjedtek a napi gyakorlatban, aminek több oka is lehet. Egyrészt a closure működése nem mindenki számára magától értetődő, másrészt az objektumorientált paradigma és annak osztálydefiníciókon alapuló szemlélete a legelterjedtebb. Egy további indok, hogy ha nem egy-két, hanem sokkal több objektumot kell létrehozni, akkor az osztály alapú megoldás előnyösebb lehet.
E megoldás azért mutattuk be, hogy lássuk, hogy Pythonban a normál, a beágyazott és closure függvényekkel nagyon sok feladatot meg lehet oldani anélkül, hogy osztályokat kelljen definiálni. Ezért is van, hogy a Python tudásépítés lépésről lépésre című e-könyv a függvényeket, azok változatait nagyon részletesen tárgyalja, és a szokásoshoz képest – a függvényeknél szerzett ismeretekre alapozva – később veszi csak sorra az osztályok definiálásáról, valamint példányok létrehozásáról szóló tudnivalók ismertetését.