Loeng 4: if valikuavaldis, rekursiivne funktsioon, kõrgemat järku funktsioon, anonüümne funktsioon ja lausend lambda, operaatorid and ja or ning andmetüübi objekti triviaalsus¶

Viimati uuendatud 7.10.2025.

Koodinäited¶

1  Pesastatud tingimuslaused ja loogika operaatorid¶

1.1  Pesastatud tingimuslaused¶

Koodibloki pesastamise all mõtleme selle paigutamist teise koodiploki sisse. Uurime kood kus puudub pesatamine:

In [1]:
x, y = 0, 2

if x == y:
    print('x ja y on võrdsed!')
elif x < y:
    print('x on väiksem kui y!')
else:
    print('y on väiksem kui x!')
x on väiksem kui y!

Siin pole midagi pesastatud. Kirjutama selle programmi ümber nii, et selle funktsionaalsus ei muutu kasutades pesastamist:

In [2]:
if x == y:  # Pesastav blokk.
    print('x ja y on võrdsed!')
else:
    if x < y:  # Pesastatud if bloki algus.
        print('x on väiksem kui y!')
    else:
        print('y on väiksem kui x!')
x on väiksem kui y!

Kood muutub raskemini loetavaks. Kui liigendame seda veelgi siis muutub olukord hullemaks:

In [3]:
if x == y: # Pesastav blokk.
    print('x ja y on võrdsed!')
else:
    if x < y:  # Pesastatud if bloki algus.
        print('x on väiksem kui y!')
    else:
        if x > y:  # Teise pesastatud if bloki algus.
            print('y on väiksem kui x!')
x on väiksem kui y!

Kuna pesastamine muudab koodi lugemise ebamugavamaks püüame seda vältida. Kuidas?

1.2  Pesastatud tingimuslaused ja loogika operaatorid¶

Loogikaoperaatorid pakuvad sageli võimaluse lihtsustada pesastatud tingimuslauseid. Näiteks saame järgmise koodi kirjutada ümber, kasutades ainult ühte tingimuslauset:

In [4]:
x = 3

if 0 < x:
    if x <= 10:
        print('x on positiive täisarv.')
x on positiive täisarv.

Funktsioon print käivitub ainult siis, kui läbime mõlemad tingimuslaused, seega saame sama tulemuse kasutades operaatorit and:

In [5]:
if 0 < x and x <= 10:
    print('x on positiive täisarv.')
x on positiive täisarv.

Antud juhul saame tinigimuslause ülesse kirjutada ka kasutades võrdlusoperaatoreid palju kompaktsemal kujul:

In [6]:
if 0 < x <= 10:
    print('x on positiive täisarv.')
x on positiive täisarv.

2  Pythoni üherealised avaldised¶

Pythoni saame paljud blokid nt. if-bloki kirjutada ruuumi kokkuhoiu ja loetavuse huvides ühes reas. Siin kursusel hakkame neid kutsuma avaldisteks.

2.1  Tingimuslik valikuavaldis¶

Python võimaldab üherealist if-blokkide, pesastatud if-blokkide, if-else ja if-elif-else blokkide esitust. if valikuavaldist ehk tingimuslause lühikuju kasutatkse näiteks funktsioonide argumendi sulgudes.

2.1.1  Konstruktsioon if-else¶

Võrdleme eelmine nädal õpitud if-else blokki süntaksit mis on kujul:

In [7]:
a = 1  # Eelmise nädala kood.

if 0 < a < 10:  # if-else blokk.
    print('1')
else:
    print('2')
1

uue tingimusliku valikuavaldisega. Nüüd kasutame seda funktsiooni print argumendina:

In [8]:
print('1' if 0 < a < 10 else '2')  # Valikuavaldis funk. print argumendina.
1
In [9]:
print('1') if 0 < a < 10 else print('2')
1

Avaldise süntaks omaette:

In [10]:
('1' if 0 < a < 10 else '2')  # Valikuavaldis.
Out[10]:
'1'
In [11]:
'1' if 0 < a < 10 else '2'  # Töötab ka ilma sulgudeta.
Out[11]:
'1'

Märkus: Konstruktsiooni või blokki kus on ainult lausend if ilma else-ta pole võimalik avaldisena esitada. Proovi.

2.1.2  Konstruktsioon if-elif-else¶

Võrdleme eelmine nädal õpitud if-elif-else blokki süntaksit mis on kujul:

In [12]:
a = 3

if a == 1:  # if-elif-else blokk
    print('1')
elif a == 2:
    print('2')
else:
    print('3')
3

uue tingimusliku valikuavaldisega. Nüüd kasutame seda funktsiooni print argumendina:

In [13]:
print('1' if a == 1 else '2' if a == 2 else '3')  # Valikuavaldis funk. print argumendi sulgudes.
3
In [14]:
print('1') if a == 1 else print('2') if a == 2 else print('3')
3

Avaldise süntaks omaette:

In [15]:
('1' if a == 1 else '2' if a == 2 else '3')  # Valikuavaldis.
Out[15]:
'3'
In [16]:
'1' if a == 1 else '2' if a == 2 else '3'  # Töötab ka ilma sulgudeta.
Out[16]:
'3'

Teatavasti saame lausendeid elif kasutada rohkem kui üks kord. Võrdleme avaldise kuju ka konstruktsiooniga if-elif-elif-else. Näiteks:

In [17]:
a = 4

if a == 1:  # if-elif-elif-else blokk
    print('1')
elif a == 2:
    print('2')
elif a == 3:
    print('3')
else:
    print('4')
4

Või ekvivalentselt:

In [18]:
tingimus_1 = a == 1  # Omistan tehte a == 1 tõeväärtuse muutujale tingimus_1.
tingimus_2 = a == 2
tingimus_3 = a == 3

if tingimus_1:  
    print('1')
elif tingimus_2:
    print('2')
elif tingimus_3:
    print('3')
else:
    print('4')
4

Eelneva kahe näitega identselt käituvad avaldisekujud on järgmised. Siis kasutame avaldist koos funktsiooniga print:

In [19]:
t1 = a == 1  # Omistan tehte a == 1 tõeväärtuse muutujale t1.
t2 = a == 2
t3 = a == 3

print('1' if t1 else '2' if t2 else '3' if t3 else '4')  # Valikuavaldis funk. print argumendi sulgudes.
4
In [20]:
print('1') if t1 else print('2') if t2 else print('3') if t3 else print('4')
4

Avaldise süntaks omaette:

In [21]:
('1' if t1 else '2' if t2 else '3' if t3 else '4')  # Valikuavaldis.
Out[21]:
'4'
In [22]:
'1' if t1 else '2' if t2 else '3' if t3 else '4'  # Töötab ka ilma sulgudeta.
Out[22]:
'4'

2.1.3  Pesastatud if blokk ja vastava avaldise võrdlus¶

Uurime koodi kujul:

In [23]:
x, y = 2, 0

if x == y:
    print('x ja y on võrdsed!')
else:
    if x < y:  # Pesastatud bloki algus.
        print('x on väiksem kui y!')
    else:
        print('y on väiksem kui x!')
y on väiksem kui x!

Kirjutame eeloleva koodi valikuavaldise kujule. Esitame avaldise funktsiooni print argumendina ja püüame eristada pesastatud blokki kasutades sulge:

In [24]:
ting_1 = x == y  # Omistan tehte x == y tõeväärtuse muutujale ting_1.
ting_2 = x < y
lause_1 = 'x ja y on võrdsed!'
lause_2 = 'x on väiksem kui y!'
lause_3 = 'y on väiksem kui x!'

print(lause_1 if ting_1 else (lause_2 if ting_2 else lause_3))  # Sisemised sulud pole vajalikud.
y on väiksem kui x!
In [25]:
print(lause_1) if ting_1 else (print(lause_2) if ting_2 else print(lause_3))  # Sulud pole vajalikud.
y on väiksem kui x!

Avaldise süntaks omaette:

In [26]:
(lause_1 if ting_1 else (lause_2 if ting_2 else lause_3))  # Sisemised sulud pole vajalikud.
Out[26]:
'y on väiksem kui x!'
In [27]:
lause_1 if ting_1 else lause_2 if ting_2 else lause_3  # Töötab ka ilma sulgudeta.
Out[27]:
'y on väiksem kui x!'

Siit näeme, et tulemus on identne konstruktsiooniga if-elif-else, vt. Lõik 2.1.2.

3  Rekursioon ja rekursiivne funktsioon (recursive function)¶

3.1  Rekursiooni mõiste¶

Rekursioon on mingi objekti kordamine ennastkopeerival teel. Reaalses elus saab rekursiooni näha näiteks veebikaamera pööramisel kuvarile, millel on pilt, vt. Joonis 1.

No description has been provided for this image
Joonis 1: Rekursiooni visuaalne näide.

Termin rekursioon on valdavalt kasutusel arvutiteaduses ja matemaatikas. Rekursioon on olukord, kus näiteks arvutiprogrammis defineeritud funktsioon kutsub iseennast välja eesmärgiga lahendada funktsioonile antud probleemi kergem variant. Teatud probleeme on rekursiooni abil lihtsam lahendada võrreldes muude meetoditega. Rekursiooni astmeid võib olla lõputult palju, aga korrektse rekursiivse funktsiooni puhul on alati defineeritud baasjuhtum, mille korral rekursioon peatub. Kui baasjuhtumit ei defineerita, siis on rekursioon lõputu.

Nagu mainitud eespool on ka Pythoni rekursiivne funktsioon selline funktsioon mille definitsioonis esineb see funktsioon ise. Kasutades matemaatikute kirjapilti saame rekursiooni üldkuju kirja panna järgmiselt:

$$f(x) \doteq g(x, f(x)),$$

siin $f$ ja $g$ on funktsioonid ja $x$ on argument või argumendid. Pythoni def blokk lubab funktsiooni definitsioonis kasutada loodavat funktsiooni rekursiivselt.

3.2  Rekursiooni näited¶

3.2.1  Rekursiivne konsooli printimine¶

Kirjutame funktsiooni mis teostab enda def blokis iseenda väljakutse. Näiteks:

In [28]:
def timer(n):
    if n == 0:  # n <= 0
        print('Piip! piip! piip!')
    else:
        print(n)
        timer(n - 1)  # Loodava funktsiooni väljakutse koodibloki sees.

timer(3)
3
2
1
Piip! piip! piip!

Mitu korda teostatakse funktsiooni timer rekursiivne väljakutse? Väljakutsete arv peab olema võrdne funktsiooni print väljakutsete arvuga. Seega, sõne 'Piip! piip! piip!' prinditakse üks kord. Funktsioon timer argument tähistab väljakutsete arvu. Kokku saame vastuseks $n$ korda:

$$1 + (n - 1) = n.$$

Keerukamates funktsioonides võib väljakutsete arv olla tunduvalt suurem. Seda kuidas mõelda programmi voost antud olukorras kommenteerime järgmises näites, vt. Lõik 3.2.2.

Kirjutame üldisema funktsiooni mis tagastab sellele etteantud meelevaldse sõne n korda:

In [29]:
def print_n_korda(s, n):
    if n <= 0:
        print()  # NB! Tühi rida.
    else:
        print(s)
        print_n_korda(s, n - 1)

print_n_korda('Juulius Tipikas', 3)
Juulius Tipikas
Juulius Tipikas
Juulius Tipikas

Antud programmi saab täiustada, eemaldades tulemusest reavahetuse ja samaaegselt kirjutada see lühemalt:

In [30]:
def print_n_korda(s, n):
    if n == 0:  # Või n <= 0.
        return  # Sama mis return None. Tagastatakse tühimärk None.
    print(s)
    print_n_korda(s, n - 1)

print_n_korda('Tipikas Juulius', 3)
Tipikas Juulius
Tipikas Juulius
Tipikas Juulius

Nagu näeme saame lausendit return kasutada funktsiooni töö lõpetamiseks.

3.2.2  Faktoriaal¶

Faktoriaal on lihtne mitte-lõpmatult ehk lõpliku rekursiivse funktsiooni näide. Positiivse täiarvu faktoriaali arvutame järgmiselt:

$$n! = n \cdot (n-1) \cdot (n-2) \cdot (n-3) \cdot \ldots \cdot 3 \cdot 2 \cdot 1,$$

kus $n \ge 0$ on täisarv ja kusjuures $0! = 1$. Näiteks

$$5! = 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 = 120.$$

Rekursiivne lahenduseeskiri on tavaliselt esitatud järgmisel kujul:

$$\boxed{n! \doteq n \cdot (n-1)!},$$

kus $n \ge 0$ on täisarv ja $0! = 1$ (baasjuhtum). Nagatiivsete täisarvude korral pole faktoriaal defineeritud. Faktoriaali lahenduseeskirja saame esitada ka kasutades kujutust kujul:

$$x_{n+1} = x_{n+1} \cdot x_{n}, \qquad x_{n+1} - x_{n} = 1,$$

kus $x_n, n \in \mathbb{Z^+}$, $x_n$ on täisarv, $n$ on iteratsiooni number ja $x_0 \ge 0$ ning kui $x_0 = 0$ siis $x_1 = 1$ (baasjuht). Samas kehtib ka

$$x_{n} = x_{n} \cdot x_{n-1}, \qquad x_{n} - x_{n-1} = 1,$$

kus $x_n, n \in \mathbb{Z^+}$, $x_n$ on täisarv, $n$ on iteratsiooni number, $n \ge 1$ (seose vasakul poolel) ja $x_0 \ge 0$ ning kui $x_0 = 0$ siis $x_1 = 1$ (baasjuht). Joonis 2 kujutab faktoriaali rekursiivset lahendust juhul $n = 5$.

No description has been provided for this image     No description has been provided for this image
Joonis 2: Tehte $5!$ rekursiivne lahendus kujutatud kahel erineval viisil.

Pythonis defineerime etteantud arvu faktoriaali leidva funktsiooni järgmiselt:

In [31]:
def factorial(n):
    if n <= 0:  # Saab panna ka n <= 1 või n == 1, siis tehakse üks rekursioon vähem.
        return 1  # Rekursiooni lõpp. Baasjuhtum.
    return n * factorial(n - 1)  # NB! Kasutame funktsiooni enda nime.

factorial(5)
Out[31]:
120

3.2.2.1  Rekursiivne funktsioon, selle kirjutamine ja programmivoog¶

Kuidas mõelda programmivoost rekursiivse funktsiooni puhul? Kas rekursiivne funktsiooni väljakutse bloki seest tähendab, et interpretaator liigub def bloki esimese rea peale ja hakkab koodi uuesti interpreteerima? Tegelikuses toimub midagi keerulisemat, kuigi allolev kood justkui lubaks nii järeldada:

In [32]:
def factorial(n):
    if n == 0:  # Näitame kõiki rekursiooni samme. Töötab ka operaatoriga <=.
        print('Sisenen if: Rekursiooni lõpp.')  # Baasjuhtum.
        return 1  # Baasjuhtum.
    print('Sisenen else')
    return n * factorial(n - 1)

factorial(5)
Sisenen else
Sisenen else
Sisenen else
Sisenen else
Sisenen else
Sisenen if: Rekursiooni lõpp.
Out[32]:
120

Kui jälgida väärtuste tekkimist programmis täpsemalt siis märkame midagi kummalist ja veidrat. Kasutame funktsiooni print väärtuste järelevaatamiseks:

In [33]:
def factorial(n):
    space = 5 * n * ' '
    print(space, f'Sisend {n}!')
    if n == 0:  # n <= 0, baasjuhtum.
        print(space, 'Väljund 1')
        return 1
    else:
        result = n * factorial(n - 1)
        print(space, 'Väljund', result)
        return result

factorial(5)
                          Sisend 5!
                     Sisend 4!
                Sisend 3!
           Sisend 2!
      Sisend 1!
 Sisend 0!
 Väljund 1
      Väljund 1
           Väljund 2
                Väljund 6
                     Väljund 24
                          Väljund 120
Out[33]:
120

Kui funktsioon kutsub iseennast välja, siis see ei tähenda, et see käivitub uuesti! Situatsioon on põhimõtteliselt sama kui teise funktsiooni poole pöördumine, ainuke asi, et peafunktsioon ja alamfunktsioon on samad! Kui funktsioon kutsub iseennast välja, siis see käivitub paralleelselt (alamfunktsiooni rollis). Peafunktsioon ootab, kuni alamfunktsioon lõpetab oma töö ära ja jätkab siis oma tööd.

Soovitus: Rekursiivse funktsiooni kirjutamine, faktoriaali arvutamise näitel

Kui jõuad kirjutamise ajal rekursiivse väljakutseni, siis selle asemel, et jälgida koodi täitmise kulgu, peaksid eeldama, et rekursiivne väljakutse töötab nagu tavaline funktsioon tagastades õige tulemuse (väärtuse), ja küsima endalt: "Kui ma eeldan, et oskan leida arvu $n − 1$ faktoriaali, kas ma saan siis arvutada ka arvu $n$ faktoriaali?" On selge, et saad, korrutades tulemuse $n$-ga. Muidugi on natuke veider eeldada, et funktsioon töötab õigesti, kui sa seda alles kirjutad. Praktikas on selline mõtlemine osutunud kõige kaslikumaks ja produktiivsemaks.

3.2.3  Rekursiivne astendamine¶

Rekursiivne funktsiooni ruututõstev funktsioon on kujul:

$$f(x) \doteq f^2(x) \equiv [f(x)]^2,$$

kus $f$ on suvaline funktsioon, $x$ on funktsiooni argument (või argumendid). Kui funktsiooniks $f$ valida konstantne funktsioon ehk konstant siis saame rekursiivse arvu ruututõstva funktsiooni kujul:

$$x \doteq x^2.$$

Rekursiivse astendamise protsessi saame kirja panna ka kasutades kujutust:

$$x_{n+1} = x_n^2,$$

või

$$x_{n} = x_{n - 1}^2,$$

kus $n \in \mathbb{Z}^+$ on kujutuse iteratsiooni number ja $x_n \in \mathbb{R}$ on reaalarv. Matemaatikast teame, kui valida algtingimuseks $x_0 < |1| \ne 0$ ja minna piirile $n \to \infty$:

$$\lim_{n \to \infty} x_n \equiv \lim_{n \to \infty} x^{2^n} = x_{\infty} = 0.$$

Seega lõpmatu rekursiooni protsess saab koonduda nulliks ehk iseennast justkui lõplikuks muuta ehk peatuda (arvu väärtuse mõttes). Kui $x_0 > |1| \ne 0$ siis koondumist ei toimu ja $x_n \to \infty$ kui $n \to \infty$. Joonis 3 kujutab osaliselt lõputut ja koonduvat rekursiooni mis tekib eelmainitud kujutuse ehk arvu $x$ ruututõstva astmefunktsiooni rekursiivsel rakendamisel.

No description has been provided for this image No description has been provided for this image
Joonis 3: Koonduv rekursiivne arvu ruutu tõstmine ilmutamata (vasakul) ja ilmutatud (paremal) kujul. Ilmutatud kuju arvutatud juhul $x_{0} = 0.2$.

Lõpmatut rekursiooni protsessi pole võimalik Pythonis teostada. Me peame rekursiooni mingil hetkel käsitsi peatama:

In [34]:
def recursive_square(x):
    print('Vahetulemus:', x)
    if x < 7e-12:  # Lõpetame rekursiooni mõistlikus kohas.
        return 0
    return recursive_square(x ** 2)

recursive_square(0.2)
Vahetulemus: 0.2
Vahetulemus: 0.04000000000000001
Vahetulemus: 0.0016000000000000007
Vahetulemus: 2.560000000000002e-06
Vahetulemus: 6.553600000000011e-12
Out[34]:
0

3.2.4  Rekursiivne loendamine¶

Rekursiivne loendamine põhineb rekursiivsete väljakutsete loendamise probleemile. Eespool Lõigus 3.2.1 kirjutasime funktsiooni timer. Kirjutame sarnase funktsiooni:

In [35]:
def countdown(n):
    if n == 0:  # n <= 0
        return None
    print(n)
    countdown(n - 1)

countdown(3)
3
2
1

Sama funktsioon kus prinditakse välja funktsioon argumendi väärtuse ja lõppolek:

In [36]:
def countdown(n):
    if n == 0:
        return 1
    space = 3 * n * ' '
    print(space, 'n =', n)
    return countdown(n - 1)

print('Final state: n =', countdown(4))
             n = 4
          n = 3
       n = 2
    n = 1
Final state: n = 1

Loeme rekursiivsed väljakutsed kokku:

In [37]:
def reverse_counter(n):
    if n == 0:
        return 0
    space = 3 * n * ' '
    print(space, 'n =', n)
    return 1 + reverse_counter(n - 1)  # Iga väljakutse lisab summale 1-e.

print('Väljakutseid kokku:', reverse_counter(4))
             n = 4
          n = 3
       n = 2
    n = 1
Väljakutseid kokku: 4

Leiame listis olevate elementide arvu kasutades rekursiooni, vrd. kolme eelmise koodirakuga ja vt. Joonis 4:

In [38]:
def counter(lst):
    if len(lst) == 0:  # Kui len(lst) == 1 siis tehakse üks rekursioon vähem.
        return 0       # Kui len(lst) == 1 siis kirjuta return 1.
    return 1 + counter(lst[1:])

lst_data = [1, 1, 1, 1]

print(counter(lst_data))
4

Matemaatiliselt saab selle funktsiooni rekursiooni kirja panna järgmiselt:

$$\text{vastus} = 1 + \biggl( 1 + \Bigl( 1 + \bigl( 1 + (\text{baasjuht} = 0) \bigr) \Bigr) \biggr) = 1 + 1 + 1 + 1 + 0 = 4.$$

Sulud tähistavad siin rekursiivseid funktsiooni counter väljakutseid. Joonis 4 kujutab visuaalselt listi elementide loendamist rekursiooni abil. Joonis vastab funktsiooni counter sisule.

No description has been provided for this image
Joonis 4: Rekursiivne listi elementide loendamise visualiseering. Funktsiooni counter rekursiivsed väljakutsed.

Märkus: Järgmise nädala loengus arutame itereerimist Pythonis. Programmi või alamprotseduuri nimetame rekursiivseks kui selle mingi osa taaskasutab iseennast. Programmi või alamprotseduuri nimetame iteratiivseks kui selles esineb mingi tegevuse kordus ehk tsükkel. Kõik eelnevad näidet rekursiivne printimine, faktoriaali arvutamine, rekursiivne astendamine ja rekursiivne loendamine on võimalik lahendada kasutades iteratsiooni.

3.2.5  Piiramata lõpmatu rekursioon ja Pythoni interpretaator¶

Mis juhtub kui püüame teostada lõpmatut rekursiooni? Muudame eelnevalt loodud funktsiooni timer:

In [39]:
def timer(n):  # Baasjuht puudub.
    timer(n - 1)

timer(4)
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
Cell In[39], line 4
      1 def timer(n):  # Baasjuht puudub.
      2     timer(n - 1)
----> 4 timer(4)

Cell In[39], line 2, in timer(n)
      1 def timer(n):  # Baasjuht puudub.
----> 2     timer(n - 1)

Cell In[39], line 2, in timer(n)
      1 def timer(n):  # Baasjuht puudub.
----> 2     timer(n - 1)

    [... skipping similar frames: timer at line 2 (2974 times)]

Cell In[39], line 2, in timer(n)
      1 def timer(n):  # Baasjuht puudub.
----> 2     timer(n - 1)

RecursionError: maximum recursion depth exceeded

Nagu näha tõstatus erisus RecursionError. Pythoni interpretaator peatab rekursiooni mis ületab rekursiooni sügavuse limiidi. Nagu näha teostati umbes 3000 rekursiivset väljakutset mis ongi maksimaalne lubatud rekursiooni sügavus. Sügavuse saavutamisel programm peatatakse. Maksimaalse rekursiooni limiidi järgivaatamine:

In [40]:
import sys  # Lausendit import pole veel õppinud.
print(sys.getrecursionlimit())
3000

Märkus: Kui su rekursiivne arvutus eeldab rohkem kui 3000 rekursiooni astet siis on võimalik eelnevat limiiti tõsta. Liiga väike või suur limiit võivad arvutuse täpsust mõjutada.

4  Kõrgemat järku funktsioon¶

Kõrgemat järku funktsioon võtab argumendina vastu funktsioone ehk funktsiooni objekte (mitte nende väärtusi) ja/või väljastab funktsioone (mitte nende väärtusi). Kõrgemat järku funktsioonid saavad olla nii meie enda loodud, Pythonisse sisseehitatud või kolmandate osapoolte loodud funktsioonid.

Kirjutame funktsiooni mis võtab argumentidena vastu muutuja ja funktsiooni:

In [41]:
def ruut(x):
    return x**2

def kõrgemat_järku_fun(x, fun):  # Eeldame, et fun on funktsioon.
    return fun(x)   # Väljastab funktsiooni väärtuse.

kõrgemat_järku_fun(3, ruut)
Out[41]:
9

Muudame funktsiooni lisades selle väljundile funktsiooni objekti:

In [42]:
def kõrgemat_järku_fun(x, fun):
    return fun(x), fun  # Väljastab väärtuse ja funktsiooni.

kõrgemat_järku_fun(2, ruut)  # Funktsioon ruut, eelmine koodirakk.
Out[42]:
(4, <function __main__.ruut(x)>)

5  Anonüümne funktsioon ja lausend lambda ($\lambda$ calculus)¶

Anonüümne funktsioon on ilma nimeta funktsioon (soovi korral võid talle siiski nime anda). Anonüümseid funktsioone kasutame tavaliselt kõrgemat järku funktsioonides. Anonüümne funktsioon defineeritakse ühes reas, süntaks on kujul:

lambda <argument või argumendid>: <funktsiooni algoritm, muud tegevused>

5.1  Näited¶

5.1.1  Identsusfunktsioon¶

Identsusfunktsioon on selline funktsioon mis tagastab enda argumendi seda muutmata, seega funktsioon on kujul:

$$ f(x) = x.$$

Pythonis defineerimine:

In [43]:
lambda x: x
Out[43]:
<function __main__.<lambda>(x)>
In [44]:
(lambda x: x)(5)  # Argumendi väärtuse edastamine.
Out[44]:
5

Anonüümsele funktsioonile saab omistada nime sidudes selle muutujaga:

In [45]:
identity = lambda x: x  # Annan nime anonüümsele funktsioonile.
identity(5)             # Kasuta nagu tavalist funktsiooni.
Out[45]:
5

See näide on ekvivalentne järgmise def blokiga:

In [46]:
def identity(x):
    return x

identity(5)
Out[46]:
5

5.1.2  Ruutfunktsioon¶

Ruutfunktsioon on antud kujul:

$$ f(x) = x^2.$$

Pythonis defineerimine:

In [47]:
lambda x: x ** 2
Out[47]:
<function __main__.<lambda>(x)>
In [48]:
(lambda x: x ** 2)(5)
Out[48]:
25

Anonüümsele funktsioonile saab omistada nime sidudes selle muutujaga:

In [49]:
square = lambda x: x ** 2
square(5)
Out[49]:
25

See näide on ekvivalentne järgmise def blokiga:

In [50]:
def square(x):
    return x ** 2

square(5)
Out[50]:
25

5.1.3  Mitme argumendiga funktsioon¶

Mitme argumendiga funktsiooni näide. Kahe arvu või muutuja summeerimine. Funktsioon omab kuju:

$$ f(x, y) = x + y.$$

Pythonis defineerimine:

In [51]:
lambda x, y: x + y  # Argumente eristame komaga.
Out[51]:
<function __main__.<lambda>(x, y)>
In [52]:
(lambda x, y: x + y)(5, 2)  # Argumentide väärtuste edastamine.
Out[52]:
7

Anonüümsele funktsioonile saab omistada nime sidudes selle muutujaga:

In [53]:
my_sum = lambda x, y: x + y
my_sum(5, 2)
Out[53]:
7

See näide on ekvivalentne järgmise def blokiga:

In [54]:
def my_sum(x, y):
    return x + y

my_sum(5, 2)
Out[54]:
7

5.2  Anonüümse funktsiooni kasutamine kõrgemat järku funktsioonis¶

Edastame kõegemat järku funktsioonile high_ord_func ruutfunktsiooni:

In [55]:
def high_ord_func(x, func):
    return func(x)  # Väljastab väärtuse.

high_ord_func(2, lambda x: x ** 2)  # Defineerin funktsiooni argumentide sulgudes.
Out[55]:
4

Lisaks eelnevale, väljastame kõegemat järku funktsioonis high_ord_func kuupfunktsiooni:

In [56]:
def high_ord_func(x, func):
    return func(x), lambda x: x ** 3  # Väljastab väärtuse ja kuupfunktsiooni.

high_ord_func(2, lambda x: x ** 2)
Out[56]:
(4, <function __main__.high_ord_func.<locals>.<lambda>(x)>)

5.3  Kõrgemat järku anonüümne funktsioon¶

Sisseehitatud funktsiooni sum summeerib sellele edastatud jada tüüpi andmetüüpi (korteež, list, jne.) salvestatud numbreid. Kasutame sisseehitatud funktsiooni sum kõrgemat järku funktsiooni argumendina:

In [57]:
(lambda x, y, fun: x + fun([x, y]))(5, 2, sum)  # Funktsioon sum kasutus argumendina.
Out[57]:
12

Kasutame funktsiooni my_sum kõrgemat järku funktsiooni argumendina:

In [58]:
my_sum = lambda x, y: x + y  # Defineerime uuesti.

(lambda x, y, fun: x + fun(x, y))(5, 2, my_sum)  # Funktsioon my_sum kasutus argumendina.
Out[58]:
12

Muudame funktsiooni lisades selle väljundile funktsiooni objekti kasutades lausendit lambda (NB! kasuta korteeži sulge):

In [59]:
fun_out = lambda x: x  # Hoian ruumi kokku.
(lambda x, y, fun: (x + fun(x, y), fun_out))(5, 2, my_sum)  # Väljastab väärtuse ja funktsiooni.
Out[59]:
(12, <function __main__.<lambda>(x)>)

Anonüümsele kõrgemat järku funktsioonile saab omistada nime sidudes selle muutujaga:

In [60]:
high_ord_fun = lambda x, fun: fun(x)  # Funktsioon argumendina.
high_ord_fun(2, lambda x: x * x)
Out[60]:
4

Sama tulemus kasutades def-return blokki:

In [61]:
def ruut(x):
    return x * x

def high_ord_fun(x, fun):  # Funktsioon argumendina.
    return fun(x)

high_ord_fun(2, ruut)
Out[61]:
4

Muudame funktsiooni lisades selle väljundile funktsiooni objekti kasutades lausendit lambda (NB! kasuta korteeži sulge):

In [62]:
high_ord_fun = lambda x, fun: (fun(x), lambda x: x)  # Funktsioon argumendina ja väljastab funktsiooni.
high_ord_fun(2, lambda x: x * x)
Out[62]:
(4, <function __main__.<lambda>.<locals>.<lambda>(x)>)

Sama tulemus kasutades def-return blokki:

In [63]:
def ruut(x):
    return x * x

def samasus(x):
    return x

def high_ord_fun(x, fun):  # Funktsioon argumendina.
    return fun(x), samasus  # Väljastab väärtuse ja funktsiooni samasus.

high_ord_fun(2, ruut)
Out[63]:
(4, <function __main__.samasus(x)>)

5.4  Rekursiivne anonüümne funktsioon¶

Selleks, et luua rekursiivne anonüümne funktsioon peame sellele nime andma ning seda funktsiooni definitsioonis kasutama. Lisaks kasutame if-else tingimuslause valikuavaldist.

Faktoriaali arvutamine:

In [64]:
fact = lambda n: 1 if n <= 1 else n * fact(n - 1)
fact(5)
Out[64]:
120

6  Operaatorid and ja or ning andmetüübi objekti triviaalsus¶

Loogikaoperaatoreid and ja or ja booli andmetüüpi tutvustasime eelmine nädal. Loogikaoperaatoreid saab rakendada kõikidele Pythoni andmetüüpide objektidele. Loogikatehete vastuse leidmiseks kasuta nn. and ja or tõeväärtustabeleid.

Meile juba teada faktid: Pythoni objektidel on omadus nimega triviaalsus. Pythoni triviaalse objekti (0, [], {}, (), '', jne.) tõeväärtus on $0$ (False) ja kõikide muude mittetriviaalsete andmetüüpide (2, [1, 2], {1, 2}, (2, 3), 'Python', jne.) tõeväärtus on $1$ (True). Objekti tõeväärtuse leidmiseks kasuta sisseehitatud konstruktorfunktsiooni bool.

Pythoni objektide triviaalsuse ja mittetriviaalsuse omadusega on seotud järgmised reeglid.

6.1  Meile uus loogikaoperaatorite kasutus¶

6.1.1  Operaator and¶

Operaatori and rakendamine triviaalsetele, mittetriviaalsetele ja sega-triviaalsusega andmetüüpide objektidele. Näiteks uurime operaatori and rakendamist sega- ning võrdse triviaalsusega sõnedele (proovi muid andmetüüpe):

In [65]:
'a' and 'b'  # = 'b'
Out[65]:
'b'
In [66]:
'' and ''  # = vasakpoolne triviaalne objekt.
Out[66]:
''
In [67]:
'' and 'b'  # = ''
Out[67]:
''
In [68]:
'a' and ''  # = ''
Out[68]:
''

6.1.2  Operaatori or¶

Operaatori or rakendamine triviaalsetele, mitte-triviaalsetele ja sega-triviaalsusega andmetüüpide objektidele. Näiteks uurime operaatori or rakendamist sega- ning võrdse triviaalsusega sõnedele (proovi muid andmetüüpe):

In [69]:
'a' or 'b'  # = 'a'
Out[69]:
'a'
In [70]:
'' or ''  # = parempoolne triviaalne objekt.
Out[70]:
''
In [71]:
'' or 'b'  # = 'b'
Out[71]:
'b'
In [72]:
'a' or ''  # = 'a'
Out[72]:
'a'

Samad reeglid kehtivad pikemate tehete puhul. Pea meeles, et operaator and on prioriteetsem kui or, vt. eelmise nädala koodinäiteid:

In [73]:
'Python' or '' and 'A'
Out[73]:
'Python'

Samad reeglid kehtivad ka erinevate andmetüüpide objektide vahel:

In [74]:
'A' or []  # Mitte-triviaalne sõne ja triviaalne list.
Out[74]:
'A'

6.2  Kasutamise näited¶

6.2.1  Näide 1: Tingimuslik sõne lisamine listi¶

Probleem: Lisa mittetühjale listile sõne 'abc'.

Alustame meile juba teadaoleva lähenemisega:

In [75]:
a, b = [], ['a']

if len(a) > 0:  # Funktsioon len on sisseehitatud funktsioon.
    a.append('abc')  # Meetod append on listi meetod.
    
if len(b) > 0:
    b.append('abc')

print(a, b)
[] ['a', 'abc']

Lahend kasutades loogikaoperaatoreid:

In [76]:
a, b = [], ['a']
(a or b or []).append('abc')
print(a, b)
[] ['a', 'abc']

6.2.2  Näide 2: Arvu paarsus¶

Meelespea: Pikemate tehete ja segatehete korral pea meeles, et operaator and on operaatorist or prioriteetsem, vt. Loeng 3 koodinäiteid.

Leiame arvu paarsuse kasutades loogikaoperaatoreid:

In [77]:
x = 2
x % 2 and 'paaritu' or 'paaris'
Out[77]:
'paaris'
In [78]:
x = 2
print(x % 2 and 'paaritu' or 'paaris')
paaris
In [79]:
x = 2
x % 2 and print('paaritu') or print('paaris')
paaris
In [80]:
paarsus = lambda x: x % 2 and 'paaritu' or 'paaris'
print(paarsus(3))
paaritu

Lisa 1¶

Lisa 1: Ruutvõrrandi lahend

















☻   ☻   ☻