2016. március 10., csütörtök

05 - Elágazások, ciklusok

Elérkeztünk oda, ami anno 3 éve nem sikerült, és végre új részt prezentálhatok a Python tutorialomból.

Az előző részekben a típusoktól és változóktól eljutottunk egészen odáig hogy képesek voltunk a felhasználótól adatot beolvasni a konzolon keresztül. Most következik az a rész, amikor végre a számítógép is csinálni fog valamit, úgyhogy jöjjenek az elágazások és ciklusok, avagy a vezérlési szerkezetek (control flow).

Elágazások

Azon kívül hogy a számítógép nagyon jól össze tudja adni a számokat, feltételeket is tud vizsgálni, és a vizsgálat eredményének függvényében a program futása különböző utakon haladhat tovább. Ezt hívjuk elágazásnak.

Elágazás létrehozásához az if (ha) utasítást kell használnunk. Nézzük hogy hogyan működik:

number = int(input('Írj be egy számot: '))
if number > 100:
    print('A szám nagyobb mint 100')

A 2. sortól érdekes a történet. Az if utasítás mellett egy olyan kifejezés következik, aminek van logikai értéke. A number > 100 kifejezés, ha a number értéke nagyobb mint 100, akkor True, amúgy False kiértékelési értéket kap. A sort mindig a : zárja, erre figyeljünk.

A 3. sorban valami tök új dolgot látunk, méghozzá hogy ez a sor kicsit beljebb kezdődik mint a többi. A Pythonban ilyen bekezdésekkel (indent, indentation) tudunk kód blokkokat (törzseket) létrehozni. Egyes utasítások, mint az if is, megkövetelik hogy új blokkot nyissunk utánuk. Ilyen utasítás nélkül, csak úgy poénból nem tologathatjuk kifelé-befelé a forráskódot kedvünk szerint, mert IndentationError hibaüzenetet kapunk.

Általában elmondható, hogy amelyik utasítás végére :-t kell írni, az alatt új blokkot kell nyitni.

Futtassuk a kódot és írjunk be egy 100-nál nagyobb számot. Válaszul az A szám nagyobb mint 100 üzenetet kapjuk.

Futtassuk még egyszer a programot, de most írjunk egy 100-nál kisebb értéket. A konzolban semmi se jelenik meg. Láthatóvá vált, hogy az if blokkja akkor kerül futtatásra, ha az if melletti kifejezés True értékű. Lehetőségünk van szerencsére rögtön, ugyan ebben a szerkezetben kezelni a False értéket is, ehhez az else (különben) utasítást kell használni.

number = int(input('Írj be egy számot: '))
if number > 100:
    print('A szám nagyobb mint 100')
else:
    print('A szám kisebb mint 100')

Az else és az if egy párt alkotnak, ezért azonos bekezdést kell alkalmazni előttük (azonos bekezdési szintent (indentation level) kell lenniük).

Futtassuk így a programot. Így akár 100-nál nagyobb, akár kisebb értéket írunk be, mindig kapunk valamilyen eredményt, viszont ha pont 100-at írunk be, akkor azt kapjuk hogy A szám kisebb mint 100, ami egyáltalán nem igaz, ezért jó lenne ezt az esetet is kezelni.

Az if és else kombinálató egy 3. utasítással, ez az elif (különben ha).

number = int(input('Írj be egy számot: '))
if number > 100:
    print('A szám nagyobb mint 100')
elif number == 100:
    print('A szám pont 100')
else:
    print('A szám kisebb mint 100')

Ez a program végre tényleg jól működik. Vegyük végig soronként hogy mi történik néhány konkrét érték esetén:

  • Ha 99-et írunk be, akkor az első feltétel (number > 100) hamis lesz, így ugrunk a következőre (number == 100), de ez is hamis. Nincs több elif ág, ezért mindenképp az else-ben kötünk ki.
  • Ha 100-at írunk be, akkor az első feltétel (number > 100) hamis lesz, így ugrunk a következőre (number == 100) ami igaz lesz, így bekerülünk ebbe az ágba.
  • Ha 101-et írunk be, akkor az első feltétel (numer > 100) rögtön igaz lesz, így nem is vizsgálódunk tovább.

Ebben a három pontban igyekeztem nyomatékosítani, de leírom külön is, hogy ha a program futása bekerült valamelyik elágazás blokkjába, akkor a blokk végével a többi feltétel már nem értékelődik ki, hanem véget ér a teljes szerkezet vizsgálata. A következő kód jól mutatja a feltételek sorrendjének fontosságát:

number = int(input('Írj be egy számot: '))

if number > 100:
    print('A szám nagyobb mint 100')
elif number > 200:
    print('A szám nagyobb mint 200')
elif number > 300:
    print('A szám nagyobb mint 300')

Ha beírjuk azt hogy 301, akkor azt kapjuk válaszul hogy A szám nagyobb mint 100, pedig az első elif ág is igaz lett volna… meg a második is… viszont az if ág előbb volt igaz. Mindezekből következik, hogy ha feltételeket írunk akkor legelőször érdemes a legszűkebb esetet írni, és ahogy haladunk lefelé, úgy lazítunk a megszorításokon.

Az elágazási szerkezetet tehát mindig if-el kell kezdeni, tetszőlegs számú elif lehet benne, és igény szerint lehet benne else, ami mindig a legutolsó ága a szerkezetnek.

Kód blokkok

Azt már tudjuk, hogy kód blokkot bekezdéssel hozunk létre. A bekezdéseinket mindig 4 db szóközzel toljuk beljebb (ahogy a fenti példáimban is tettem). A blokk mindig addig tart ameddig azonos bekezdésű sorok követik egymást. Az üres sorok nem zárják le a blokkot, és nem kell beléjük felesleges szóközöket írni.

Ezen a szellős kódon ez jól látszódik:

number = int(input('Írj be egy számot: '))

if number % 2 == 0:
    print('A szám páros')

    print('A szám fele:', number / 2)
else:
    print('A szám páratlan')

    print('A szám kétszerese:', number * 2)

print('Ezt gépelted be:', number)

Akár kevesebb vagy több szóközt, sőt, szóköz helyett tabulátort is használhatunk, de a programozók nagy többsége, és a Python nyelv fejlesztői is a 4 db szóköz használatát ajánlják.

Ciklusok

A programozási nyelvekben a ciklus (loop) azt jelenti, hogy a számítógép valamilyen feltétel függvényében egy blokknyi kódot folyamatosan újra és újra lefuttat.

A Python nyelvben kétféle ciklus létezik: a while és a for.

A while ciklus

A while (amíg) utasítás klasszikusan valósítja meg azt a definíció-szerűséget, amivel ezt a fejezetet bevezettem: addig futtatja a blokkjában szereplő kódot, amíg a neki adott kifejezés logikailag igaz értékű. Lássuk az ide vágó példát is: írjuk ki a számokat 1-10-ig.

counter = 1
while counter <= 10:
    print(counter)
    counter = counter + 1

Az 1. sorban deklaráltuk a counter változót 1 értékkel.

A 2. sor az érdekes: a while melletti feltételétől függ, hogy meddig kell a while blokkját ismételtetni. Ezt magyarul bentmaradási feltételnek szokták hívni.

Nézzük lépésenként hogy mi történik innentől:

  1. A program futása a while counter <= 10-re került. Itt a counter értéke 1, így a kifejezés egyenlő a 1 <= 10-el, ami True-ra értékelődik ki. Így belépünk a blokkba.
  2. Kiírjuk a counter értékét.
  3. A counter változ értékét növeljük 1-el. Ez nagyon fontos.
  4. Véget ért a blokk, vissza ugrunk a while sorára.
  5. A counter értéke most már 2, így a kifejezés egyenlő a 2 <= 10-el, ami True-ra értékelődik ki. Így újra belépünk a blokkba.

Ez a fenti játék egészen addig fut, amíg a counter el nem éri a 11-et, akkor ugyanis:

  1. A counter értéke 11, így a kifejezés egyenlő 11 <= 10-el, ami False-ra értékelődik ki, így nem lépünk be a blokkba.
  2. A blokkon kívüli forráskóddal folytatódik a program.

Látható volt, hogy a while kifejezése még ez előtt kiértékelődött mielőtt beléptünk volna újra a blokkba. Ezért nem jelent meg a 11 a konzolban. A ciklust e tulajdonsága miatt előltesztelő ciklusnak hívják.

Azt amikor egyszer lefut a ciklus blokkja iterációnak (iteration) hívják.

Írjunk egy olyan programot, ami addig olvas be és ad össze tört számokat, amíg egy üres Enter-t nem nyom a felhasználó.

sums = 0

next_number = input('Írj be egy számot: ')
while next_number != '':
    sums = sums + float(next_number)
    next_number = input('Írj be egy számot: ')

print('Az összeg:', sums)

A sums változóban tároljuk majd a részösszeget, (tehát minden iterációban ehhez a változóhoz adjuk majd a begépelt számokat). Ez után kiírjuk az Írjon be egy számot: üzenetet, és a begépelt szöveget eltároljuk a next_number változóban, majd következik is a ciklus.

A feltétel szerint addig ismétlődik a ciklus, amíg a next_number értéke nem üres string. Ilyet akkor kapunk, a nem írunk be semmit, csak egy üres Enter-t nyomunk. Ha rögtön ezzel kezdett a felhasználó, akkor be se lépünk a ciklusba hanem szépen elballagunk mellette. Viszont ha begépelt valamit (akármit), akkor már bent is vagyunk a blokkban.

Itt float-á alakítjuk a next_number értékét, és ezt hozzá adjuk a sums változóhoz, így növeltük a részösszeget (azért használunk float-ot, hogy tört számokkal is működjön a program). A ciklus vége a lényeg: itt szólítjuk fel újra a felhasználót, hogy gépeljen be egy számot, és az eredményt ugyan abba a next_number változóba írjuk, ahova a cikluson kívül is tettük. A futás vissza ugrik a while sorára, ezzel véget ért egy iteráció. Innen pedig az utoljára begépelt értéktől függően vagy belépünk újra a ciklusba, vagy kihagyjuk és megérkezünk az összeg kiírásához.

Ciklus megszakítása, új iteráció indítása

Létezik két speciális utasítás, amit csak ciklusok törzsében lehet csak használni: a continue és a break.

A continue utasítás hatására azonnal új iterációt indít a ciklus. Ez például akkor lehet hasznos, ha át akarunk ugrani egy értéket.

counter = 0

while counter <= 10:
    counter = counter + 1

    if counter % 2 == 0:
        print(counter, 'páros')
        continue
    print(counter, 'páratlan')

Ez egy igen béna kis példa, de a lényeg benne van: ha a counter változó értéke 2-vel osztható, akkor kiírjuk hogy páros és indítjuk is az új iterációt. Ha az érték páratlan, akkor sosem lépünk be a feltétel törzsébe, és megjelenik a páratlan felirat.

A break utasítás hatására azonnal kiugorhatunk a ciklusból Ez akkor jön jól, ha még az előtt be akarnánk fejezni a ciklust, hogy az természetesen (értsd: a feltételében megadott kifejezés függvényében) befejeződne. Általában végtelen ciklus esetén van erre szükség mert hát amúgy… végtelen sokáig menne a ciklus.

A végtelen ciklus

Előfordulnak olyan szituációk, amikor véletlenül vagy direkt olyan kifejezést írunk, ami örökké igaz lesz. Ilyenkor a ciklus soha sem áll le; ezt hívják végtelen ciklusnak (infinite loop). Vegyük a legelső példa kódot ebből a részből. Ha a végéről elhagynánk a counter növelését, akkor a ciklus feltétele örökké igaz lenne, hisz az 1 mindig kisebb a 10-nél; így a program örökké és koxon azt írná a konzolba hogy 1.

Ez elsőre tök haszontalannak tűnhet, de ha egyszerűen nem tudjuk hogy mikor kéne véget érnie a ciklusnak, akkor a legjobb egy végtelen ciklust indítani. Tipikusan ilyen eset az, amikor a felhasználótól várunk valamilyen értéket (vagy a hálózatról).

Vegyük a korábbi példát. Az igaz hogy nincs benne végtelen ciklus, de sajnos az a gond vele hogy redundáns (ismétlés van benne): az input() kétszer, ráadásul azonos üzenettel van meghívva benne.

Úgy is megközelíthettük volna a problémát, hogy mivel nem tudjuk hogy mennyi számot gépelnek majd be, ezért egy végtelen ciklust indítunk, és minden iterációban felszólítjuk a gépelésre a felhasználót. Viszont ha a begépelt érték egyszer egy üres string lesz, akkor jól kiugrunk a ciklusból.

sums = 0

while True:
    next_number = input('Írj be egy számot: ')
    if next_number == '':
        break
    else:
        sums = sums + float(next_number)

print('Az összeg:', sums)

A while True: sor egy gyakran visszatérő “jelenség” Pythonban. Végtelen ciklusokat általában így szoktak indítani (természetesen állhatna it 1 == 1, 1 < 2, stb, de tartsuk magunkat a többiekhez).

A ciklus törzsének nagy része szerintem nem érdemel magyarázatot. A break utasításnál, bár egy feltételben szerepel, de a feltétel a ciklusban van, így ide írható ez az utasítás. Az értelmezésével pedig természetesen a ciklusból, és nem a feltételből ugrunk ki (olyat nem lehet csinálni).

Ezt a mintát (pattern) használják a Pythonban a hátultesztelő ciklusok szimulálására, mert nem létezik külön szerkezet erre az esetre.
Hátultesztelő ciklusnak azt nevezzük, amikor a ciklus törzse fut le először, és csak utána döntjük el, hogy következhet-e iteráció.

Azon kívül hogy mindenki a while True-val indít végtelen ciklust, ennek a kifejezésnek a kiértékelése nem terheli a CPU-t.

A break-es példám annyira nem, de a continue eléggé izzadságszagú, nem véletlenül: általában ki lehe kerülni a break és continue használatát.

Tömb elemeinek kiírása

Zárásul írjunk egy ciklus ami kiírja egymás alá egy tömb elemeit:

names = ['Alice', 'Bob', 'Carol']

index = 0
while index < len(names):
    print(names[index])
    index = index + 1

Újdonság a 4. sorban lehet: a len() függvény. Ez a függvény visszaadja a neki adott objektum hosszát. list esetén ez az elemek száma. Ha tudjuk hogy mennyi elem van a tömbben, akkor nagyon egyszerűen indítunk egy ciklus, ami egyesével elszámol addig, ez kerül az index változóba, és ezzel a változóval indexszeljük a tömböt. Ennyi az egész.

Talán mire leíram a forráskódot már a ti fejetekben is összeállt a probléma megoldása, aminek nagyon örülök, és tök jó. Pythonban viszont soha, de soha nem így járjuk be a tömb elemeit. Hadd mutassam meg nektek a valóságot.

A for ciklus

A for ciklus a Python programozási nyelvben úgynevezett iterálható objektumok (iterables) bejárására (iterálására, iterate) való. Azok az objektumok amiken értelmezve az index operátor általában iterálatóak is. Ilyen a list, a dict és a str is.

Akik más programozási nyelvekből már ismerik a for ciklust, azoknak ez szokatlan lehet.

Vegyük a legutolsó példát: írjuk ki egy tömb elemeit for ciklussal:

people = ['Alice', 'Bob', 'Carol']

for name in people:
    print(name)

Azt a kurva azért ez nem kicsit szexibb. A for ciklus feje két részből tevődik össze:

  1. A for és in között szerepel annak a változónak (vagy változóknak!) a neve, amin keresztül el akarjuk érni az aktuális elemet (ez a példában a name nevet kapta). Itt nem szerepelhet másféle kifejezés.
  2. Az in után következik az objektum neve (vagy tetszőleges kifejezés), amit iterálunk (a példában ez a people változó)

Iteráljunk egy olyan list-et, ami list-eket tartalmaz.

people = [[1990, 'Alice'], [1991, 'Bob'], [1992, 'Carol']]

for name in people:
    print(name)

A képernyőn kis két elemű tömbök jelennek meg, tehát a for nem mászik bele a belső tömbökbe.

A Pythonban lehetőség van rá, hogy egy tömb elemeit egy sornyi változóba kicsomagoljuk, ez az unpacking. A módszer a következő:

people = [[1990, 'Alice'], [1991, 'Bob'], [1992, 'Carol']]

for year, name in people:
    print(year, name)

Így az első iterációban a [1990, 'Alice']-ból az 1990-et a year, az 'Alice'-t pedig a name változóba vezethetjük, és így tovább a későbbi iterációk során.

Nézzünk meg egy str-t is:

for c in 'Python':
    print(c)

A str iterálása során karakterenként járjuk végig az objektumot. Tehát a képernyőn egymás alatt jelennek meg a Python szó betűi.

Nézzünk egy dict-et is:

people = {1990: 'Alice', 1991: 'Bob', 1992: 'Carol'}
for year in people:
    print(year, people[year])

A dict iteráláskor a dict kulcsait járja be a ciklus. Azért hogy látszódjanak a kulcsokhoz tartozó értékek is, kézzel meg kellett indexelni people objektumot.

Lehetséges azonban az is, hogy egyszerre iteráljuk a kulcsot és az értéket is. Ehhez a kód:

people = {1990: 'Alice', 1991: 'Bob', 1992: 'Carol'}
for year, name in people.items():
    print(year, name)

A year, name bizonyára már ismerős. A people.items() viszont tök új. Ez egy úgynevezett metódus (method), ami párokban vissza adja a people elemeit, e miatt írhatunk unpackinget a for és in közé.

Erről a metódus témáról egyenlőre ennyit elég tudni, de sejthető, hogy ez valami függvény-szerűség, hisz nagyon hasonlít rájuk. Nagyon sokat fogunk még -nem is olyan túl sokára- beszélni erről.

A for is csak egy ciklus, így használhatóak benne a break és continue utasítások.

A range()

Levezetésnek bemutatnék egy új függvényt, a range()-et. Ez a függvény szám-intervallumok generálására való.

A while-os példákban többször is használtuk a counter változót, amit deklarálni és növelni kellett, más különben nem működött volna a ciklusunk normálisan. Ha ilyen számlálós ciklusokat akarunk írni, akkor a for-al és a range()-el gyorsan és kényelmesen letudhatjuk a problémát.

Írjuk ki így a számokat 1-10-ig:

for counter in range(1, 11):
    print(counter)

Igen, ennyi az egész.

A függvény 3 féle argumentumot tud fogadni, de mindig csak int értékek használhatóak. Itt egy pár példa az összes esetre:

  1. range(5): Előállítja a számokat 0-4-ig. Olyan mint ha azt írnánk hogy [0, 1, 2, 3, 4].
  2. range(5, 10): Előállítja a számokat 5-9-ig. Olyan mint ha azt írnánk hogy [5, 6, 7, 8].
  3. range(5, 10, 2): Előállítja a számokat 5-9-ig kettesével. Olyan mint ha azt írnánk hogy [5, 7].

A 3. argumentum lehet negatív szám is, ilyenkor visszafelé számolunk. Természetesen ekkor az első értéknek nagyobbnak kell lennie a másodiknál:

for i in range(10, 0, -1):
    print(i)

Így tudunk 10-től 1-ig vissza számolni egyesével.

Ha print()-el kiírjuk a range() függvény visszatérési értékét, akkor meglepő módon azt kapjuk a konzolba hogy range(). Ez azért van, mert valójából a range() nem tér vissza semmilyen list-el, amit a for iterálhat, hanem ő maga egy olyan objektum, ami minden iterációban más értéket produkál. Majd mi is írunk ilyet csodát nemsokára.

Zárás

Azt, amikor olyan programot írunk, amiben egymást követő utasítások, elágazások és ciklusok vannak, struktúrált programozásnak (structured programming) nevezik. Python nyelv más stílusú programozást is támogat, mint pl.: a funkciónálist vagy az objektum-orientáltat. Azzal viszont hogy idáig eljutottunk, nem csak a Python, de a legtöbb programozási nyelv alapjait is megismertük.

A következő fejezetben mélyítjuk a Pythonos típusokról szerzett tudásunkat úgy, hogy azokat hasznosítani tudjuk a ciklusokban és elágazásokban.

Ez a bejegyzés a Python tutorialom egyik része. Az összes rész listája itt fellelhető.

-slp

Nincsenek megjegyzések:

Megjegyzés küldése