Félre a tréfát, programozzunk Pythonban!

2018.03.06. · tudomány

Itt az ideje, hogy a tettek mezejére lépjünk, és írjunk végre egy önálló, működő programot. De még mielőtt ennek nekifutnánk, tisztázzuk, hogy egyelőre csak olyan programok jöhetnek számításba, amik nem rajzolnak csili-vili dolgokat a képernyőnkre, sőt, egyáltalán nem olyanok, mint amilyeneket állandóan használunk a telefonunkon és a gépeinken, nem használják ki a grafikus lehetőségeket, nem nyitnak ablakokat, és nem vezérelhetők egérrel vagy érintőképernyővel. Ilyen programot írni ugyanis egyelőre, az eddigi eszközeinkkel bonyolult lenne, például mert mások által már megírt programokat, könyvtárakat vagy csomagokat kellene hozzá használni. Szóval csak olyan programot tudunk írni, amit egy karakteres terminál-ablakbanegy billentyűzettel beírt paranccsal (majd az billentyű lenyomásával) tudunk elindítani, és a működését is csak azon látjuk, amit a teminál-ablakba kiír. (Ha valaki sosem használt még ilyen terminál-ablakot, ahova parancssorokat lehet beírni, akkor kérjen segítséget valakitől, hogy azt hogyan kell. Nem nehéz.)

Hogyan írjuk a programot?

Kézzel fogjuk file-okba beleírni a dolgokat, valamilyen editorral (ami nem összetévesztendő az ún. szövegszerkesztőkkel, az editor csak azokat a karaktereket illeszti bele a file-ba, amiket mi megadunk, semmi plusz információt). Például a Windows operációs rendszerben a Notepad ilyen editor.

Arra is készüljünk fel, hogy az első programunk semmi látványosat nem fog csinálni, sőt, még közel érdekeset sem, különben túl bonyolult lenne, de legalábbis sokkal tovább tartana elmagyarázni, mint ez az írás. A legtöbb bevezető leírásban vagy kurzuson először egy olyan programot írnak, ami csak annyit ír ki a képernyőre, hogy „Hello, world!”, annyira, hogy a Hello, world kifejezés mára a semmi érdekeset nem csináló példaprogram neve lett. Én azért azt javaslom, egy kicsit többet tegyünk, mint hogy kiíratjuk azt, hogy „Kukucs!” Folytassuk a francia kártyás példánkkal, de egyelőre ne csináljon mást a programunk, mint hogy sorolja fel mind az 52 lapot, sorrendben. Vagyis semmi mást nem teszünk, mint a 0-diktól az 51-ik lapig (ugye, az világos, hogy mindig 0-tól kezdjük a számolást?) mindegyiket kinyomtatjuk a képernyőre.

photo_camera Grafika: Tóth Róbert Jónás

Különböző okokból ezt az első programot a Python programozási nyelven fogjuk megírni. (Ráadásul feltételezni fogom, hogy a 3. verziója van az olvasónál is telepítve.) A Python is azok közé a nyelvek közé tartozik, amiket korábban funkcionálisnak neveztem: az eljárásokat függvényeknek tekintjük, amiknek argumentumaik vannak – ezeket függvényszerű jelöléssel (a függvény neve után zárójelben, vesszőkkel elválasztva) írjuk –, és lehet egy visszatérési értékük. Továbbá olyan nyelv, amiben nem kell mindennek (adatoknak, eljárásoknak) a típusát előre bejelenteni, deklarálni, ami rövidíti a leírást, de nehezíti a hibák megtalálását. Formai szempontból a Pythonnak az a különlegessége, hogy az utasítások sorozatát (az ún. blokkokat) nem „{” és „}” jelek közé írjuk, hanem úgy jelöljük, hogy az előző sor végére kettőspontot írunk, és utána a blokk utasításait az előző sornál beljebb kezdve, egymás alá írjuk, és a blokk végét az jelöli, hogy az új sort nem kezdjük így beljebb.

Aztán lenyomjuk az Enter billentyűt

A modularitást most egy kicsit elhanyagoljuk, csak annyit teszünk meg, hogy külön file-ba írjuk a fő programfile-unkat, és külön file-ba a benne használt adatokat és eljárásokat. Ebben a nyelvben nem kell jelölnünk, hogy mi a fő eljárás, annak nem lesz neve, hanem egyszerűen a fő programfile-unkba egymás után beírt utasítások alkotják a fő eljárást, ezeket hajtja majd végre a program, amikor elindítjuk. Legyen ennek a fő programfile-nak a neve oszt.py (bár a lapokat még nem osztjuk ki igazán, mert nem keverjük meg a paklit). A másik file-t, amibe az adatok és eljárások részleteit írjuk, nevezzük francia.py-nek (mert a francia kártyáról szól).

A programunkat majd úgy tudjuk elindítani, hogy a parancssorba a „python oszt.py” parancsot írjuk be (és aztán lenyomjuk az billentyűt). (Vannak programozási nyelvek, rendszerek, ahol nem ilyen egyszerű egy programot lefuttatni, mert előzőleg a rendszerrel fel kell dolgoztatni, le kell fordíttatni az összes programfile-unkat, és így jön létre a végrehajtható file, de ezzel egyelőre nem kell törődnünk.) Maga az oszt.py file valahogy így fog kinézni:

# Hogy a francia.py file-ban bevezetett szimbólumokat
# itt használhassuk:
import francia

# utasítások...

(A Python nyelvben a „#” jel azt jelenti, hogy a rendszer ne vegye figyelembe, ami ettől a jeltől a sor végéig tart, ez csak magunknak és másoknak szóló komment.) Játékból (és hagyományőrzésből) megtehetjük, hogy mondjuk az „import francia” sor helyett (mert a francia.py file-t még nem készítettük elő) azt írjuk oda, hogy „print( "Kukucs!" )”, és lefuttatjuk. (A print egy olyan eljárás neve, ami egy sorba kiírja a képernyőre az argumentumát.) Működni fog, de ennél most tovább szeretnénk menni, úgyhogy a kísérletünk után töröljük ezt a sort.

Például írhatunk egymás alá két utasítást, az egyik azt fejezi ki, hogy előállítunk egy pakli kártyát (a lapokat sorbarakva), a második meg azt, hogy kinyomtatjuk a lapokat:

# Hogy a francia.py file-ban bevezetett szimbólumokat
# itt használhassuk:
import francia

lapok = francia.pakli()
francia.nyomtat( lapok )

Itt a lapok szimbólumot csak arra használjuk, hogy a létrehozott paklit megnevezzük, hogy aztán kiadhassuk a kinyomtatására felszólító utasítást. De ezt a két dolgot egyetlen utasításba is beleírhatjuk:

# Hogy a francia.py file-ban bevezetett szimbólumokat
# itt használhassuk:
import francia

francia.nyomtat( francia.pakli() )

Persze ahhoz, hogy ez működjön, létre kell hoznunk és ki kell töltenünk a francia.py file-t, és ebben be kell vezetnünk legalább a pakli és a nyomtateljárásokat. Tehát az a file kb. így fog kinézni:

def pakli():
# törzs: utasítások blokkja...

def nyomtat( l ):
# törzs: utasítások blokkja...

Remélem, ebben semmi meglepő sincs, a Python nyelvben az eljárások leírását (definícióját) a def kulcsszóval kell kezdeni, utána jön az eljárás neve, utána zárójelben a formális paraméterek, utána pedig egy blokk az eljárás törzsével. A file-unk persze még mást is tartalmazhat, olyan szimbólumokat is bevezethetünk, amelyek „nyilvánosak”, a külvilág számára láthatóak (hogy úgy mondjam: részei a modul interfészének), meg olyanokat is, amik csak ezen a file-on belül használhatóak (a Pythonban az a szabály, hogy ez utóbbiaknak a nevét két aláhúzással kell kezdeni).

Korábban már tárgyaltam, hogy milyen módokon ábrázolhatnánk a kártya lapjait, a színeiket és a lapértéküket; megállapítottuk, hogy egy paklit például egy lapokból álló tömbbel ábrázolhatunk. Most mindezt egy kicsit elfelejtjük, és radikálisan leegyszerűsítünk mindent. A Pythonban van egy lista nevű adattípus, amivel nagyon könnyű bánni, és ami hasonlít a tömbökre (de például az elemeinek nem kell egyforma adattípusba tartozniuk). A lapokat egyszerűen a 0-tól 51-ig terjedő egész számok fogják ábrázolni, a pakli az ezekből álló lista lesz. Egy lap színét az a szám fogja ábrázolni, ahányszor a lapnak megfelelő számban megvan a 13, az értékét pedig ugyanennek az osztásnak a maradéka. A Pythonban van egy range nevű eljárás, aminek a visszatérési értéke éppen egy egész számokból álló lista. Ha csak egy számot adunk meg az argumentumaként, akkor a 0-tól addig a számig tartó számlista lesz az értéke (de az argumentum már nem lesz ebben benne). Így a pakli eljárást ilyen egyszerűen definiálhatjuk:

def pakli():
return( range( 52 ) )

Ebből az is látható, hogy a Pythonban a függvényként tekintett eljárásokat az jellemzi, hogy van bennük egy return utasítás, amiben megadjuk, hogy mi lesz a visszatérési érték, itt ez a bizonyos számlista, amiben 0-tól 51-ig vannak a számok.

A nyomtatáshoz bevezethetünk egy-egy szimbólumot a színeknek és a lapértékeknek megfelelő stringekhez, azokat is egy-egy listában tároljuk. A Pythonban a listákat úgy is megadhatjuk, hogy az elemeiket szögletes zárójelek között, vesszőkkel elválasztva felsoroljuk:

__szinek = ["♣", "♢", "♡", "♠"]
__ertekek = ["2", "3", "4", "5", "6", "7", "8", "9", "10"
, "B", "D", "K", "A" ]

Mint látható, olyan neveket adtam ezeknek a listáknak, amik két aláhúzással kezdődnek, tehát ezek a szimbólumok nem lesznek nyilvánosak.

A lapok kinyomtatásához végig kell haladnunk a paklinak megfelelő listán, vagyis egymás után többször ugyanazt el kell végeznünk (annyi különbséggel, hogy minden körben más-más lapot nyomtatunk ki). Az ilyen ismételgetést a számítástudományban ciklusnak (angolul: loop) nevezzük. A Pythonban ez nagyon egyszerű, most leírom, hogyan definiálhatjuk a nyomtat nevű eljárást, és abból világos lesz:

def nyomtat( l ):
for lap in l:
szin = lap // 13
ertek = lap % 13
print( __szinek[szin] + __ertekek[ertek] )

Ezzel a programunk készen is van, de beszéljük meg, hogy miket figyelhetünk meg a nyomtat definíciójában. Először is, ahogy egy lista elemein végighaladhatunk:

for in :

Másodszor, hogy a „hányszor van meg benne” művelet (maradékos osztás) jele a Pythonban „//”. Harmadszor, hogy a listák valahanyadik elemét ugyanazzal a szögletes zárójeles jelöléssel kaphatjuk meg, ahogy a tömböknél szokás (pl. a __szinek[2] a 2 számú színnek, vagyis a kőrnek megfelelő string, a __szinek lista harmadik eleme, mert 0-tól kezdjük a számolást). Negyedszer, hogy két stringből a „+” művelet segítségével egyetlen stringet állíthatunk elő, ez a két string egymás után rakását (konkatenációját) jelenti.

A változók tovább élnek

Végül még egy nagyon fontos dolog. Ahogy feljebb mondtam, a Pythonban nem deklaráljuk előre, hogy melyik szimbólum milyen típusú adatot jelöl. Ezért nem kellett külön leírnunk, hogy a nyomtat eljárásnak az l szimbólummal jelölt argumentuma egy egész számokból álló lista, ezért a törzsében szereplő lap szimbólum egy egész számot jelöl, ahogy a szin és az ertek szimbólumok is. Ugyanakkor ezeknek a deklarációknak a hiánya elfedi azt, hogy mi ezeknek a szimbólumoknak az „élettartama”, vagyis hogy az l szimbólumot csak a definíció törzsén belül fogjuk (ebben az értelmében) használni, a lap szimbólumot csak a for kezdetű cikluson belül, a szin meg az ertek szimbólumokat meg csak az utánuk következő print utasításban. Ezeknek a szimbólumoknak az élettartama, ún. hatóköre (angolul: scope) mind csak ilyen szűk. Ha lennének deklarációink, akkor mi is, a programot feldolgozó rendszer is jobban látná, hogy hol születnek meg ezek az ún. változók, és hogy csak annak a blokknak a végéig élnek, amiben a deklarációjuk van. (A Pythonban egyébként ez pont nem igaz, ott a változók tovább élnek, például a fenti példában a lap változó a ciklus után is életben marad – de a definíción kívül nem –, de ez mellékes, és inkább zavaró tulajdonsága ennek a nyelvnek.)

A következő részben egy másik nyelven a programozásnak egy másfajta megközelítését mutatom be, és elkezdjük megírni ugyanezt a programot abban a másféle megközelítésben.

A szerző nyelvész, az MTA Nyelvtudományi Intézetének főmunkatársa.