Turuxi-py:Tapaaminen 19.11.2009

Kohteesta Turuxi
Loikkaa: valikkoon, hakuun

Aiheina:

  • funktioita
  • iteraatio

Linkkejä:

Tehtävien tarkistus

Edellisen kerran tehtävät käytiin pikaisesti läpi.

Hedelmällisiä funktioita

Funktion suorituksen voi keskeyttää return-lauseella, mutta hedelmälliset funktiot palauttavat jonkin arvon pelkän funktion suorittamisen loppumisen lisäksi. Arvo palautetaan myös return-lauseella.

return arvo

Funktiossa voi olla useampia return-lauseita, jotka suoritetaan erilaisissa tapauksissa. Hedelmällisessä funktiossa, eli funktiossa, jonka halutaan aina palauttavan jotain, täytyy olla tarkkana, että funktion jokainen haara aina päättyy return-lauseeseen. Esimerkiksi seuraavassa funktiossa arvolla x=0 ei palauteta mitään.

def iteisarvo(x):
    if x<0:
        return -x
    elif x>0:
        return x

Jos funktio ei palauta mitään return-lauseella, palauttaa se oletusarvon None. Tämä on Python-kielen olio, jota käytetään kuvaamaan olion puutetta.

Toinen epätoivottu tilanne on se, että ohjelmassa on niin kutsuttua kuollutta koodia, eli ohjelmarivejä, joihin on mahdotonta koskaan päästä. Seuraavassa esimerkki kuolleesta koodista:

def kuollut(x):
    return x
    if x=0:
        return -x

Vähittäinen ohjelmointi

Return-lauseen unohtaminen on usein toistuva ohjelmointivirhe. Tätä voidaan yrittää välttää esimerkiksi vähittäisellä ohjelmoinnilla (incremental programming), jossa funktio kasvatetaan vähitellen toimivaksi. Ensimmäiseksi kirjoitamme funktion otsakkeen ja return-lauseen, joka palauttaa oikeaa tyyppiä olevan arvon. Tällöin voidaan myös kehittää mielekkäästi muita funktioita, jotka käyttävät hyväkseen tätä funktiota.

Rakennetaan vähitellen funktio, joka palauttaa kahden pisteen (x1,y1) ja (x2,y2) välisen etäisyyden.

def distance(x1, y1, x2, y2):
    return 0.0

Nyt funktio toimii jo, vaikka vastaus onkin väärä:

>>> distance(1,2,4,6)
0.0

Lisätään ohjelmakoodia ja testataan sen jälkeen. Jos funktio ei toimi, tiedämme missä vika on.

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    print "dx is", dx
    print "dy is", dy
    return 0.0

Muuttujiin dx ja dy lasketaan pisteiden x- ja y-suuntaiset etäisyydet ja tulostetaan ne. Kun tämän funktion suorittaa, nähdään, että välitulokset ovat oikein (3 ja 4) ja voidaan jatkaa seuraavaan vaiheeseen.

>>> distance(1,2,4,6)
dx is 3
dy is 4
0.0

Seuraavaksi lasketaan muuttujien dx ja dy neliöiden summa ja vaihdetaan sen arvo tulostukseksi.:

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    print "dsquared is: ", dsquared
    return 0.0

Tulosteena on:

 >>> distance(1,2,4,6)
 dsquared is:  25
 0.0

Lopulta lasketaan muuttujan dsquared neliöjuuri (korotetaan potenssiin 0.5), jolloin saadaan Pythagoraan lauseen mukainen oikea vastaus. Vastaus palautetaan return-lauseella.

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    result = dsquared**0.5
    return result

Totuusfunktiot

Funktiot voivat palauttaa boolen-arvoja siinä, missä muitakin tyyppejä. Tällaisia True- tai False-arvoisia funktioita sanotaan totuusfunktioiksi ja ne ovat käytännöllisiä, kun halutaan esimerkiksi piilottaa monimutkainen if-lauseen ehto ja näyttää ehdon merkitys selkeänä funktion nimenä. Totuusfunktiot nimetäänkin usein tarkastelemansa ominaisuuden mukaan muotoon: is_ominaisuus(). Esimerkiksi:

def is_divisible(x,y):
    if x % y == 0:
        return True
    else:
        return False

Tämä funktio voitaisiin toki kirjoittaa lyhemminkin, kun huomataan, että ehdon molemmissa haaroissa palautettava totuusarvo on juuri sama kuin ehdon arvo:

def is_divisible(x,y):
    return x % y == 0

function-tyyppi

Pythonissa funktiot ovat tyypin function muuttujia. Tämä tyyppi on siis yhtä kelvollinen tyyppi kuin esimerkiksi int, float ja str. Tämä tarkoittaa sitä, että funktioita voi sijoittaa muuttujiin ja antaa toisille funktioille argumentteina siinä kuin muitakin tyyppejä. Tarkastellaan funktiota foo. Tässä kohtaa täytyy huomata ero käsitteiden foo ja foo() välillä. Näistä ensimmäinen on itse funktio foo ja jälkimmäinen tarkoittaa arvoa, jonka foo:n suorittaminen palauttaa.

Esimerkki:

def f(n):
    return 3*n - 6

def g(n):
    return 5*n + 2

def h(n):
    return -2*n + 17

def doto(value, func):
    return func(value)

print doto(7, f)
print doto(7, g)
print doto(7, h)

Tuloksena:

15
37
3

Docstring

Tässä kohtaa käytiin läpi kolmella lainausmerkillä muodostettavat merkkijonot, jotka voivat sisältää rivinvaihtoja:

jono = """Merkkijono jossa
on mukana pari
rivinvaihtoa."""

Tällaisia merkkijonoja käytetään myös dokumentoimaan funktioiden toimintaa kirjoittamalla kuvaus funktiosta heti otsakkeen jälkeen.

def foo():
    """Tämä funktio ei tee mitään."""
    return

Hyvin kirjoitetuista docstringeistä pystytään sopivilla työkaluilla generoimaan suoraan ohjeman funktioiden dokumentaatio.

Docstringejä on käytetty myös testitapausten luomiseen doctesteillä.

Muuttujien päivittäminen

Samaan muuttujaan voi tehdä sijoituksia uudelleen ja niiden arvoja voi sijoittaa toisiinsa.

>>> a = 4
>>> b = a
>>> a = 2
>>> print a, b
2 4

Koska Python laskee (evaluoi) aina ensin sijoituslauseen oikean puolen ja vasta sen jälkeen sijoittaa tuloksen vasemmalla olevaan muuttujaan, voi sama muuttuja esiintyä sijoituslauseen molemmilla puolilla.

>>> a = a + 1
>>> print a
>>> 3

Muuttujan arvoa voidaan siis muuttaa hyödyntämällä muuttujan arvoa.

while-silmukka

Muuttujan arvon päivittämistä voidaan käyttää hyödyksi rakennettaessa toistettavia silmukoita while-lauseella.

def countdown(n):
    while n > 0:
        print n
        n = n-1
    print "Blastoff!"

Silmukassa muuttujan n arvo on aluksi jotain ja kullakin silmukan kierroksella tätä arvoa pienennetään, kunnes while-lauseen ehto ei enää toteudu.

Silmukoita tehdessä tulee olla varovainen ja varmistaa, että sen suoritus päättyy joskus. Usein silmukan ehdosta ja muuttujien päivityksistä on helppo nähdä, että silmukan loppu saavutetaan. Ehkä kuitenkin vielä useammin silmukasta on hyvinkin vaikea päätellä varmasti sen päättymistä. Silmukan toimintaa onkin syytä suunnitella tarkkaan jo ennen ohjelmakoodin kirjoittamista.