Az előző bejegyzésben szó volt a generátorról. Azt egy olyan speciális iterátorként jellemeztük, amely lehetővé teszi a kétirányú adatmozgást a hívó kliens program és a generátor között. E tulajdonságot lehet kihasználni korutinok megvalósítására.
A korutinok olyan kódszerkezetek, amelyek egymást kölcsönösen meg tudják hívni úgy, hogy egy adott feladatot együttműködve megvalósítsanak. Ezért nevezik az ilyen kódszerkezeteket korutinoknak (coroutin), vagyis együttműködő programoknak (cooperative routines). Az együttműködés megvalósításához a korutinok nélkülözhetetlen tulajdonsága, hogy képesek a kódvégrehajtást – az állapotuk megőrzése mellett – akár többször is felfüggeszteni, és átadni a vezérlést egy másik programblokknak, majd kérésre folytatni az utasításvégehajtást attól a ponttól, ahol abbamaradt. Képesek továbbá minden folytatáskor adatot fogadni, illetve minden egyes programvégrehajtás felfüggesztésekor adatot átadni.
A korutinok működését egy egyszerű példával illusztráljuk. Kártyajátékot fogunk modellezi, amelyet három játékos játszik francia-kártyával. A megkevert kártyapakliból minden játékos kezdésnek 5 lapot kap. A maradék paklit az asztalra tesszük színével felfelé. A játékosok egymás után meghatározott sorrendben következnek. A szabály az, hogy a sorra kerülő játékosnak mindig az asztalon levő pakli tetején látható kártya színével megegyező, vagy ha nincs ilyen, akkor értékével egyenlő vagy értékesebb kártyát kell a kezéből az asztali pakli tetejére lerakni. Ha nem tud lerakni semmit, akkor fel kell húznia a kezébe a pakli tetején levő kártyát. Az nyer, akinek a kezéből legelőször fogynak el a kártyák.
A megvalósítás egy lehetséges programkódját láthatjuk alább.
|
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 77 78 |
from collections import namedtuple from random import shuffle Kártya = namedtuple('Kártya', 'szín érték') kártyaszínek = (chr(u) for u in range(0x2660, 0x2664)) kártyapakli = [Kártya(szín, érték) for szín in kártyaszínek for érték in range(2, 15)] def játékos(pakli: list[Kártya]): """Korutint megvalósító generátorfüggvény""" eldobott_kártya = None while True: felső_lap: Kártya = yield eldobott_kártya egyező_színűek = {k for k in pakli if felső_lap.szín == k.szín} nagyobb_értékűek = {k for k in pakli if k.érték >= felső_lap.érték} if egyező_színűek: eldobott_kártya = egyező_színűek.pop() elif nagyobb_értékűek: eldobott_kártya = nagyobb_értékűek.pop() else: eldobott_kártya = None pakli.append(felső_lap) # Ha az eldobás után nem marad több kártya, ezt kivételdobással # jelezzük a hívó program felé. if eldobott_kártya is not None: pakli.remove(eldobott_kártya) if not pakli: raise GeneratorExit # A kártyapaklit jól megkeverjük. for i in range(10): shuffle(kártyapakli) # létrehozzuk a játékosokat és mindegyik 5 lapot kap. játékosok = [*zip(('Anna', 'Béla', 'Cecília'), [játékos(kártyapakli[:5]), játékos(kártyapakli[5:10]), játékos(kártyapakli[10:15])])] kártyapakli = kártyapakli[15:] # A pakliban a kiosztottakkal kevesebb marad. # Korutinok kezdőállapotba hozása. for _, j in játékosok: j.send(None) # Játék while True: # A soron következő játékos aktuális_játékos = játékosok.pop(0) név, player = aktuális_játékos print('\nA soron köv játékos', név) # Az asztali pakli tetején levő kártya. felső_kártya: Kártya = kártyapakli[-1] print(f'Az asztalon levő pakli legfelső lapja: ' f'{felső_kártya.szín}{felső_kártya.érték}') # A játékos vagy el tud dobni egy megfelelő kártyát vagy ha nem, # akkor felveszi az asztali pakli tetején levő kártyát. # Ha laplerakás után nincs a kezében már semmi, akkor nyert. try: lerakott_kártya: Kártya = player.send(felső_kártya) except GeneratorExit: print(f'{név} nyert, mert az utolsó lapját is le tudta tenni.') break if lerakott_kártya is None: kártyapakli.remove(felső_kártya) print(f'{név} nem tudott lerakni kártyát, ezért ' f'felhúzta a felső lapot.') else: kártyapakli.append(lerakott_kártya) print(f'{név} által az asztalra lerakott kártya: ' f'{lerakott_kártya.szín}{lerakott_kártya.érték}') játékosok.append(aktuális_játékos) |
A programban először létrehozzuk az 52 db kártyát egy-egy mezőneves tuple objektumként. A játékosokat egy-egy generátor alapú korutin fogja megvalósítani. Ehhez egy generátorfüggvényt definálunk, amely a kezdeti 5 db kiosztott kártyát egy listában fogadja. A függvénytörzsön belül a yield bal oldalán álló változóba fog kerülni az asztali pakli legfelső lapja, a jobb oldalán pedig a lerakott lap, ha van. Ha nincs, akkor ez None értékű lesz. A továbbiakban ellenőrizzük, hogy van-e lerakható lap. Ha van, akkor egyet kiválasztunk. Elsősorban az egyező színűt rakjuk le. Ha ilyen nincs, akkor választunk az értékek alapján megfelelőkből. Ha az eldobás után nem marad kártya az adott játékosnál, akkor ezt egy kivételdobással jelezzük. A kártyapakli megkeverése után létrehozzuk a játékosokat, és az azokat reprezentáló korutinokat kezdőállapotba hozzuk. Utána következhet maga a játék. Itt egy ciklusban sorban egymás után vesszük ki a játékosokat az azokat tartalmazó listából. Beazonosítjuk az asztali pakli legfelső lapját, és átadjuk a játékosnak a send() metódussal. Ha a játékos nem tud lerakni kártyát, akkor a legfelső lapot meg kell tartania, így eltávolítjuk a pakli tetejéről. Ha tudott lerakni, akkor az eldobott kártyát a pakli tetejére helyezzük. Ha a játékos köre befejeződött, akkor őt a lista végére tesszük, hogy újra rá tudjon kerülni a sor. Ha nyert valaki, akkor a kivételdobást lekezeljük és kiírjuk a nyertes nevét, ami után a játék véget ért (kilépünk a ciklusból).
Megjegyezzük, hogy a játék nem mindig tud nyertest eredményezni, ilyenkor nem áll le a futás. Lehetne erre is kódokat írni, és még számos helyen finomítani, de az ilyen extra kódok elvinnék a fókuszt az alapvető célról, ami most a korutin működésének bemutatása volt. A korutinokról és a példaprogramban szereplő egyéb nyelvi elemekről a Python tudásépítés lépésről lépésre című e-könyvben további részleteket olvashatunk.