Alapvető vezérlőutasítások

Bonyolultabb programok sok egymás után következő utasítás végrehajtásából állnak. Azt, hogy melyik utasítás mikor kerül végrehajtásra, a vezérlőutasítások határozzák meg. Minden program nyelvben két alapvető vezérlő utasítás mindenképpen megtalálható, a feltételválasztó és a ciklusszervező.

Az if utasítás

Az if utasítás tölti be a legtöbb nyelvben a feltételválsztó utasítás szerepét. Segítségével határozhatjuk meg, hogy bizonyos körülmények teljesülésekor a program a megfelelő utasításokat hajtsa végre. Lássunk erre egy példát:


In [1]:
if 2+2==4:
    print('A matematika még mindig működik')


A matematika még mindig működik

Figyelem, a második sort beljebb húztuk! A behúzás a Python jelölése az utasítások csoportosítására!

A behúzás mértékére általánosan bevett szokás, hogy a szintaktikailag alacsonyabb szintű programrészek négy szóközzel beljebb vannak tolva. Ha tehát két if utasítást ágyazunk egymásba, akkor a második által teljesítendő utasítások 8 szóközzel lesznek beljebb húzva. Ez a behúzás a jupyter notebook kódcelláiban automatikusan is megtörténik. A : utáni ENTER leütése után a következő sor 4 szóközzel kerül beljebb, további ENTER leütése hatására a hierarchia azonos szintjén kezdjük az új sort. Ha feljebb szeretnénk lépni, akkor a behúzást ki kell törölni. A C, C++, Java nyelvekben kapcsos zárójeleket {} használnak az egybefüggő kódrészletek elkülönítésére, kb. így:

if (i==10) {
print i
}

A FORTRAN nyelvben az END szócska kiírása jelzi a kódrészlet végét. A pythonban a kettőspont (":"), majd szóközökkel beljebb írt sorok szolgálnak erre. Ha más programokból másolunk át részleteket, figyeljünk arra, hogy a behúzás helyett nem TAB-ot használ-e. (A TAB néhol megengedett, de kerülendő. A modern python kódolási stílusirányzat minden behúzást, ahogy azt fent is említettük, 4 szóköznek javasol.)


In [2]:
today='Monday';
time='12:00';
if today=='Monday':
    if time=='12:00':
        print('Nyomassuk a pythont!')


Nyomassuk a pythont!

Bonyolultabb kritériumszerkezetet az else és elif parancsok segítségével konstruálhatunk:


In [3]:
x = 1
if x < 0:
    x = 0
    print('Negatív, lecseréltem nullára')
elif x == 0:
    print('Nulla')
elif x == 1:
    print('Egy')
else:
    print('Egynél több.')


Egy

Hiányozhat, de lehet egy vagy akár egynél több elif rész, az else rész szintén elmaradhat. Az elif kulcsszó – amely az ‘else if’ rövidítése – hasznos a felesleges behúzások elkerülésre. Egy if ...elif ... elif ... sor helyettesíti a más nyelvekben található switch és case utasításokat.

A for utasítás

A számítógépek legfontosabb tulajdonságai közt szerepel az, hogy nagyon gyorsak és "fáradhatatlanok". Olyan feladatok megoldásában a leghatékonyabbak, amikor a feladatot kevés munkával meg lehet fogalmazni ("az alkotó pihen") de végrehajtása nagyon sok ismétlést, iterációt igényel ("a gép forog"). Az iteráció (angol: iterate) azt jelenti, hogy például egy lista elemein egyesével végigmegy a program, és műveleteket végez el rajtuk. A Python-ban az egyik erre használható utasítás a for parancs (magyarul kb. a ...-ra, azaz pl. a hét minden napjára, a lista minden elemére):


In [4]:
days_of_the_week = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]

In [5]:
for day in days_of_the_week:
    print(day)


Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Ez a kódrészlet a days_of_the_week listán megy végig, és a meglátogatott elemet hozzárendeli a day változóhoz, amit ciklusváltozónak is neveznek. Ezek után mindent végrehajt, amit a beljebb tabulált (angolul: indented) parancsblokkban írtunk (most csak egy print utasítás), amihez felhasználhatja a ciklusváltozót is. Miután vége a beljebb tabulált régiónak, kilép a ciklusból. Annak semmi jelentősége nincs, hogy a példában a day nevet adtuk az iterációban szereplő ciklusváltozónak. A program semmit se tud az emberi időszámításról, például, hogy a hétben napok vannak és nem kiscicák:


In [6]:
for macska in days_of_the_week:
    print(macska)


Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

A ciklus utasításblokkja állhat több utasításból is:


In [7]:
for day in days_of_the_week:
    statement = "Today is " + day
    print(statement)


Today is Sunday
Today is Monday
Today is Tuesday
Today is Wednesday
Today is Thursday
Today is Friday
Today is Saturday

A range() parancs remekül használható, ha a for ciklusban adott számú műveletet szeretnénk elvégezni:


In [8]:
for i in range(20):
    print(i," szer ",i ,"az pontosan ",i*i)


0  szer  0 az pontosan  0
1  szer  1 az pontosan  1
2  szer  2 az pontosan  4
3  szer  3 az pontosan  9
4  szer  4 az pontosan  16
5  szer  5 az pontosan  25
6  szer  6 az pontosan  36
7  szer  7 az pontosan  49
8  szer  8 az pontosan  64
9  szer  9 az pontosan  81
10  szer  10 az pontosan  100
11  szer  11 az pontosan  121
12  szer  12 az pontosan  144
13  szer  13 az pontosan  169
14  szer  14 az pontosan  196
15  szer  15 az pontosan  225
16  szer  16 az pontosan  256
17  szer  17 az pontosan  289
18  szer  18 az pontosan  324
19  szer  19 az pontosan  361

Akkor válik mindez még érdekesebbé, ha az eddig tanult iterációt és feltételvizsgálatot kombináljuk:


In [9]:
for day in days_of_the_week:
    statement = "Today is " + day
    print(statement)
    if day == "Sunday":
        print ("   Sleep in")
    elif day == "Saturday":
        print ("   Do chores")
    else:
        print ("   Go to work")


Today is Sunday
   Sleep in
Today is Monday
   Go to work
Today is Tuesday
   Go to work
Today is Wednesday
   Go to work
Today is Thursday
   Go to work
Today is Friday
   Go to work
Today is Saturday
   Do chores

Figyeljük meg, a fenti példában hogy ágyazódik egymás alá a for és az if!

Egy példaprogram: Fibbonacci-sorozat

A Fibonacci-sorozat első két eleme 0 és 1, majd a következő elemet mindig az előző kettő összegéből számoljuk ki: 0,1,1,2,3,5,8,13,21,34,55,89,...

Ha nagyobb $n$ értékekre is ki akarjuk számolni a sorozatot, ez kiváló feladat lehet egy fáradhatatlan és gyors számítógépnek!


In [10]:
n = 10 # ennyi elemet szeretnénk meghatározni
sequence = [0,1] # az első két elem
for i in range(2,n): # számok 2-től n-ig, Figyelni kell, hogy n ne legyen kisebb mint 2!!
    sequence.append(sequence[i-1]+sequence[i-2])
print (sequence)


[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Nézzük végig lépésről lépésre! Először $n$ értékét, azaz a kiszámolandó sorozat hosszát állítjuk be 10-re. A sorozatot majdan tároló listát sequence-nek neveztük el, és inicializáltuk az első két értékkel. A "kézi munka" után következhet a gép automatikus munkája, az iteráció.

Az iterációt 2-vel kezdjük (ez ugye a 0-s indexelés miatt a 3. elem lesz, hisz az első kettőt már mi megadtuk) és $n$-ig, a megadott listaméretig számolunk.

A ciklus törzsében az addig kiszámolt lista végére hozzátűzzük (append parancsról kicsit bővebben itt) az előző két tag összegét. A ciklus vége után kiíratjuk az eredményt.

Függvények

Számos programozási nyelvben szokás a felhasználó által megalkotott bonyolultabb utasításokat függvényekbe szervezni. Jól megírt függvények használata általában rövidebbé és jobban átláthatóvá teszi a programokat.

Függvények deklarálása

Ha más hosszúságú sorozatot szeretnénk, átmásolhatjuk a fenti kódot egy új cellába, és átírhatjuk az n=10-et pl. n=100-ra. Van azonban egy hatékonyabb módszer, új függvény-ként definiálhatjuk a def utasítás segítségével:


In [11]:
def fibonacci(sequence_length):
    "A Fibonacci sorozat elso *sequence_length* darab eleme" # ez csak a 'help'-hez kell
    sequence = [0,1]
    if 0 < sequence_length < 3:
        return sequence[:sequence_length]
    for i in range(2,sequence_length): 
        sequence.append(sequence[i-1]+sequence[i-2])
    return sequence

Most már meghívhatjuk a fibonacci() függvényt különböző hosszakra:


In [12]:
fibonacci(5)


Out[12]:
[0, 1, 1, 2, 3]

Elemezzük a fenti kódot! A már megszokott módon a kettőspont és behúzás határozza meg a függvénydefinícióhoz tartozó kódblokkot. A 2. sorban idézőjelek közt szerepel a "docstring", ami a függvény működését magyarázza el röviden, és később a help paranccsal hívható elő:


In [13]:
help(fibonacci)


Help on function fibonacci in module __main__:

fibonacci(sequence_length)
    A Fibonacci sorozat elso *sequence_length* darab eleme

A notebookos környezetben a docstring a ? segítségével is elérhető:


In [14]:
?fibonacci

A docstring-et jupyter környezetben úgy is megtekinthetjük, ha egyes függvények hasában, azaz a zárójelek között SHIFT+TAB-ot nyomunk. Próbáld ki ezt az alábbi cellán (annélkül hogy lefuttatnád azt)!


In [ ]:
fibonacci()

A függvény kimenetelét a return kulcsszó határozza meg. Ha a fügvény definiálása során nem használtunk return utasítást akkor a függvény None (semmi) értéket ad vissza. Ha egy függvény lefutott, és nem hajtott végre return utasítást, akkor is None értékkel tér vissza. A fibonacci fügvény például egy listát ad vissza:


In [15]:
x=fibonacci(10)
x


Out[15]:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Ez a függvény viszont nem tér vissza sehol:


In [16]:
def ures_fuggveny(x):
    print('Én egy ures fuggvény vagyok,\nannak ellenére hogy beszélek,\nnem térek vissza változóval!!')
    y=x-2;

Így a z válltozóban nem tárolódik semmilyen érték!


In [17]:
z=ures_fuggveny(3)
z


Én egy ures fuggvény vagyok,
annak ellenére hogy beszélek,
nem térek vissza változóval!!

In [18]:
print(z)


None

Egy függvénynek lehet több bemeneti változója is:


In [19]:
def osszead(a,b):
    return a+b

Az is előfordulhat, hogy több értéket szeretnénk visszakapni egy függvényből. Ezt például az alábbiak alapján tehetjük meg:


In [20]:
def plusminus(a,b):
    return a+b,a-b

In [21]:
p,m=plusminus(2,3)
print (p)
print (m)


5
-1

Paraméter lista és a "kicsomagolás"

Előfordulhat, hogy egy függvénynek sok bemenő paramétere van, vagy hogy egy függvény bemenő paramétereit egy másik függvény eleve egy listába rendezi. Egy tipikus ilyen példa, amint azt későb látni fogjuk, a függvényillesztés esete. Ilyenkor a paramétereket tartalmazó lista "kicsomagolásával", amit a *-jellel tehetünk meg, kompaktabb kódot érhetünk el. Vegyünk például egy olyan esetet amikor egy adatsorra egy ötödfokú polinomot illesztünk.

$$f(x)=a_0+a_1x+a_2x^2+a_3x^3+a_4x^4+a_5x^5$$

Definiáljunk egy függvényt ami egy "futó" $x$ változó és az ötödfokó polinomnak megfelelően hat darab "paraméter" $a_i$ változó segítségével kiértékeli a fenti polinomot:


In [22]:
# ez lesz az illesztendő függvény
def poly5(x,a0,a1,a2,a3,a4,a5):
    return a0+a1*x+a2*x**2+a3*x**3+a4*x**4+a5*x**5

Az illesztés során hat darab illesztési paramétert határozunk meg: $a_0,a_1,a_2,a_3,a_4,a_5$ ám ezeket az illesztő program egy listába rendezve adja a kezünkbe:


In [23]:
# ezek az illesztés során meghatározott paraméterek
# az alábbi sorrendnek megfelelően
# params=[a0,a1,a2,a3,a4,a5]
params=[ 2.27171539, -1.1368942 ,  0.65380304, -0.25005187, -0.1751268 , -0.48828309];

Ha ki szeretnénk értékelni az illesztett polinomot az $x=0.3$ helyen, akkor azt megtehetjük az alábbi módon:


In [24]:
poly5(0.3,params[0],params[1],params[2],params[3],params[4],params[5])


Out[24]:
1.9801329481213

vagy az ennél sokkal kompaktabb módszerrel:


In [25]:
poly5(0.3,*params)


Out[25]:
1.9801329481213

Ez a konstrukció lehetővé teszi, hogy a függvény definiálása során felkészísük a függvényt arra, hogy a bemenő paraméterek száma ne legyen rögzített. Ha a függvény deklaráció során egy paraméter elé *-t teszünk akkor az a paraméter tetszőleges hosszúságú lehet! Vizsgáljuk meg az alábbi példát:


In [26]:
def adok_mit_kapok(*argv):                                            #Így, a *-al, készítünk fel egy függvényt 
                                                                      #válltozó számú paraméter fogadására
    print("Nekem ",len(argv),"db bemenő paraméterem jött")
    for arg in argv:
        print ("Ez egy paraméter :", arg)
    return argv[-1]                                                 #a bemenő paraméterek elemeire mint 
                                                                    #szokványos lista elemekre hivatkozunk

A fenti blokkban definiált függvény tetszőleges számú bemenő paramétert elfogad! A futás során közli, hogy hány paraméter érkezett, kiírja azokat, illetve a paraméterlista utolsó tagját mint a függvény vissza térési értékét állítja be.


In [27]:
adok_mit_kapok('Gáspár','Menyhért','Boldizsár')


Nekem  3 db bemenő paraméterem jött
Ez egy paraméter : Gáspár
Ez egy paraméter : Menyhért
Ez egy paraméter : Boldizsár
Out[27]:
'Boldizsár'

Természetesen a tetszőleges paraméter helyett egy "kicsomagolt" tetszőleges hosszúságú listát is használhatunk!


In [28]:
adok_mit_kapok(*params) #természetesen itt is működik a kicsomagolás..


Nekem  6 db bemenő paraméterem jött
Ez egy paraméter : 2.27171539
Ez egy paraméter : -1.1368942
Ez egy paraméter : 0.65380304
Ez egy paraméter : -0.25005187
Ez egy paraméter : -0.1751268
Ez egy paraméter : -0.48828309
Out[28]:
-0.48828309

Függvények kulcsszavakkal

Azon kívül, hogy a szótárak már önmagukban is igen hasznos adatstruktúrák, később látni fogjuk, hogy sokszor függvények bizonyos paramétereit is szokás szótárakba szedni. Az ilyen paramétereket szokás kulcsszavas változóknak vagy kulcsszavas argumentumnak (angol nyelven keyword argument) hívni. Ezek használata olvashatóbbá és emberi szemmel is jóval értelmezhetőbbé teszi a programot, ezenfelül további flexibilitást is ad a programozó kezébe. Nézzük ezt meg az alábbi példán:


In [29]:
#Így adunk meg alapértelmezett értékeket
def students(ido, allapot='lelkesen figyelik a tanárt', tevekenyseg='kísérletezés', ora='fizika'):
    print("Ezek a diákok "+ora+"órán "+tevekenyseg+" közben mindig "+allapot+"!");
    print("Még akkor is, ha épp ",ido,"-t mutat az óra!");

A függvény első paraméterét meg kell adnunk, ha nem adunk többet, akkor az alapértelmezett értékek ugranak be:


In [30]:
students('17:00')


Ezek a diákok fizikaórán kísérletezés közben mindig lelkesen figyelik a tanárt!
Még akkor is, ha épp  17:00 -t mutat az óra!

Ha egy kulcsszavas argumentumot kap a függvény, akkor azt értelemszerűen használja:


In [31]:
students('8:00',allapot='unott képet vágnak')


Ezek a diákok fizikaórán kísérletezés közben mindig unott képet vágnak!
Még akkor is, ha épp  8:00 -t mutat az óra!

A kulcsszavas argumentumok sorrendjére nem kell figyelni:


In [32]:
students('8:00',ora='ógörög',allapot='pánikolva izzadnak',tevekenyseg='TÉMAZÁRÓ')


Ezek a diákok ógörögórán TÉMAZÁRÓ közben mindig pánikolva izzadnak!
Még akkor is, ha épp  8:00 -t mutat az óra!

Ha a deklaráció során nem használt kulcsszót adunk meg, akkor hibát kapunk:


In [33]:
students('17:00',tanar='Mici néni')


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-33-7769c15fcb73> in <module>()
----> 1 students('17:00',tanar='Mici néni')

TypeError: students() got an unexpected keyword argument 'tanar'

Hasonlóan problémába ütközünk, ha egy kulcsszót kétszer is alkalmazunk:


In [34]:
students('8:00',ora='ógörög',ora='kémia')


  File "<ipython-input-34-f0bd085991b6>", line 1
    students('8:00',ora='ógörög',ora='kémia')
                                   ^
SyntaxError: keyword argument repeated

Kulcsszavas argumentumok szótárának kicsomagolása **-jel segítségével történik:


In [35]:
diak_hozzaallas={'ora':'ének','allapot':'nyüszítenek'};
students(12,**diak_hozzaallas)


Ezek a diákok énekórán kísérletezés közben mindig nyüszítenek!
Még akkor is, ha épp  12 -t mutat az óra!

Amint a sima paramétereknél is, itt is előfordulhat az, hogy tetszőleges hosszúságú dict-et szeretnénk feldolgozni. Erre példa, ha egy olyan függvényt írunk, amely esetleg több más fügvényt hív, melyeknek tovább akarjuk adni a bejövő paraméterek egy részét.

Az alábbi függvény egy tetszőleges szótárat vár a bemenetre, megnézi hogy milyen hosszú, és ha a szótárban van 'hamburger' címkéjű kulcsszó, akkor annak az értékét adja vissza:


In [36]:
def kulcsot_adok_amit_kapok(**szotar):
    print('A szotár hossza:',len(szotar))
    for kulcs in list(szotar.keys()):
        if kulcs=='hamburger':
            print('Van hamburger!')
            return szotar[kulcs]

In [37]:
kulcsot_adok_amit_kapok(makaróni=1,torta='finom') #itt már nem jelent hibát ha előre meg nemhatározott kulcsszavaink vannak!


A szotár hossza: 2

In [38]:
kaja={'makaróni':1,'torta':'finom','hamburger':137,'saláta':'nincs'}
kulcsot_adok_amit_kapok(**kaja)


A szotár hossza: 4
Van hamburger!
Out[38]:
137

Általános függvénydeklarációs szokások

Amint azt a fentiekben láttuk, függvényeknek többféleképpen is adhatunk paramétereket,

  1. változók (ezek lehetnek sima változók, meghatározott hosszúságú listák, vagy akár kulcsszavas változók is)
  2. előre meg nem határozott hosszúságú változó lista
  3. kulcsszavas változók tetszőleges hosszú listája

Sokszor előfordul, hogy egy függvénynek mind a négy fajta bemenete is lehet. Illetve hogy néhány változónak alapértelmezett értéket is adunk. Függvények definiálásánál használjuk a fenti sorrendet!

Például:


In [39]:
def bonyolult_fuggveny(valtozo1,valtozo2,valtozo3='ELZETT',*args,**kwargs):
    if ((len(args)==0 and len(kwargs)==0)):
        return valtozo3+str(valtozo2)+str(valtozo1)
    elif (len(args)!=0 and len(kwargs)==0):
        return 'Van valami az args-ban!'
    elif (len(args)==0 and len(kwargs)!=0):
        return 'Van valami az kwargs-ban!'
    else:
        return 'Mindenféle változónk van:'+str(valtozo1)+str(valtozo2)+valtozo3+str(args)+str(kwargs)

A fenti függvény első két változója "sima" változó, a harmadik egy kulcsszavas változó az alapértelmezett 'ELZETT' értékkel, és ezen kívül megengedünk még egyéb tetszőleges hosszú "sima" változók listáját (args), illetve tetszőleges hosszú kulcsszavas változók listáját (kwargs). Nézzük meg hogy a notebook során korábban definiált változókat felhasználva milyen viselkedést mutat a fent deklarált függvény:


In [40]:
bonyolult_fuggveny(1,2)


Out[40]:
'ELZETT21'

In [41]:
bonyolult_fuggveny(1,2,valtozo3='MULTLOCK')


Out[41]:
'MULTLOCK21'

In [42]:
bonyolult_fuggveny(1,2,*days_of_the_week)


Out[42]:
'Van valami az args-ban!'

In [43]:
bonyolult_fuggveny(1,2,**kaja)


Out[43]:
'Van valami az kwargs-ban!'

In [44]:
bonyolult_fuggveny(1,2,*days_of_the_week,**kaja)


Out[44]:
"Mindenféle változónk van:12Sunday('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'){'torta': 'finom', 'hamburger': 137, 'makaróni': 1, 'saláta': 'nincs'}"

A Lambda-formák ☠

Egy függvénynek nemcsak változókat, hanem más függvényeket is megadhatunk bemenetként. Például gondolhatunk egy olyan függvényre, ami egy matematikai függvényt ábrázol! Ilyen esetekben, amikor egy függvény a bemenetére másik függvényt vár, sokszor előfordul, hogy hosszadalmas külön definiálni a bemeneti függvényt. Ekkor tömörebb használni az úgynevezett lambda-formákat. Nézzünk erre egy példát! Definiáljunk egy függvényt, ami egy másik függvényt értékel ki egy adott helyen, kiírja a kiértékelési helyet, és visszatér a kiértékelt függvényértékkel:


In [45]:
def funfun(g,x):
    print('Ez volt az x változó: ',x)
    return g(x)

In [46]:
def fx(x):
    return x**2-1/x;
funfun(fx,0.1)


Ez volt az x változó:  0.1
Out[46]:
-9.99

Ekkor egy úgynevezett lambda-forma segítségével alkalmazhatjuk az ennél kicsivel kompaktabb kifejezést (megúszva a függvénydeklarációt):


In [47]:
funfun(lambda x:x**2-1/x,0.1)


Ez volt az x változó:  0.1
Out[47]:
-9.99

A fenti kifejezésben a

lambda x:x**2-1/x

kódrészlet egy "anonim" függvényt definiál, amely az $x$ változóhoz hozzárendeli az $x^2-1/x$-et.

A lambda-kifejezésekkel lehet többváltozós függvényeket is definiálni. Például az alábbi

lambda x,y:x**2-1/y

kifejezés a

def fxy(x,y):
    return x**2-1/y

függvénnyel ekvivalens.