A rövid válasz, hogy 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 (enclosing function) á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 egy 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álunk, 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. Az utóbbit 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 bank_account_management(): # 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. accounts = {} def open_account(account_number): """Létrehoz egy számlát az adott számlaszámmal.""" accounts.setdefault(account_number, 0) def deposit(account_number, amount): """A megadott összeggel növeli az adott számla egyenlegét.""" if account_number in accounts: accounts[account_number] += amount return amount return 0 def withdraw(account_number, amount): """A megadott összeggel csökkenti az adott számla egyenlegét.""" if account_number in accounts: if (balance := accounts.get(account_number)) >= amount: accounts[account_number] = balance - amount return amount return 0 def get_balance(account_number): """Visszaadja az adott számla egyenlegét.""" if account_number in accounts: return accounts.get(account_number) return 0 # A visszatérési érték a beágyazott függvényobjektumok tuple konténere. return open_account, deposit, withdraw, get_balance # TESZT # Számlakezelő egyedi closure függvények. open_acc, deposit_to_acc, withdraw_from_acc, acc_balance = bank_account_management() # Számlakezelő függvények mezőneves tuple attribútumaiként. Bank = namedtuple('Bank', 'open_account deposit withdraw get_balance') bank = Bank(*bank_account_management()) # Megnyitandó számlák számlaszámai és kezdeti betétek. account_numbers = (111, 222) deposits = (10000, 250000) # Számlák kezelése az egyedi closure függvényekkel. for account_number, deposit_amount in zip(account_numbers, deposits): open_acc(account_number) print(f'Betét = {deposit_to_acc(account_number, deposit_amount)}') print(f'Számlaszám: {account_number}, egyenlege = {acc_balance(account_number)}') print(f'Kivét = {withdraw_from_acc(account_number, 50000)}') print(f'Számlaszám: {account_number}, egyenlege = {acc_balance(account_number)}') # Számlák kezelése mezőneves tuple konténer attribútumaival. for account_number, deposit_amount in zip(account_numbers, deposits): bank.open_account(account_number) print(f'Betét = {bank.deposit(account_number, deposit_amount)}') print(f'Számlaszám: {account_number}, egyenlege = {bank.get_balance(account_number)}') print(f'Kivét = {bank.withdraw(account_number, 50000)}') print(f'Számlaszám: {account_number}, egyenlege = {bank.get_balance(account_number)}') # 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 egyéni osztály definiálásán alapuló megközelítést alkalmaztunk volna, mert mint bizonyára tudjuk, Pythonban a nem nyilvánosnak szánt 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.