Prava akcija je v točki 7. Vendar vseeno pojdi do tja po korakih - predvsem, če še nikoli nisi delal takšnih reči.

Med objavljenimi nalogami jih je še nekaj, vendar si bomo to pogledali naslednjič (v bistvu gre za dekoratorje), zato bom te spoilerje izpustil.

1. Tabeliranje kotnih funkcij

Napiši funkcijo tabeliraj(xs), ki izpiše tabelo z vrednostmi funkcij sin, cos in tan pri x-ih iz seznama `xs. Funkcijo bi lahko poklicali, recimo, z

tabeliraj([x / 10 for x in range(10)])

pa bi izpisala

x    sin    cos    tan
0.0  0.0    1.0    0.0
0.1  0.09983341664682815    0.9950041652780257  0.10033467208545055
0.2  0.19866933079506122    0.9800665778412416  0.20271003550867248
0.3  0.29552020666133955    0.955336489125606   0.3093362496096232
0.4  0.3894183423086505     0.9210609940028851  0.4227932187381618
0.5  0.479425538604203      0.8775825618903728  0.5463024898437905
0.6  0.5646424733950354     0.8253356149096783  0.6841368083416923
0.7  0.644217687237691      0.7648421872844885  0.8422883804630793
0.8  0.7173560908995228     0.6967067093471655  1.029638557050364
0.9  0.7833269096274833     0.6216099682706645  1.260158217550339

Med "stolpci" so tabulatorji. Ne vznemirjaj se, če bo tvoj izpis še grši od tega tule, saj tole ne bo vaja iz oblikovanja. (Če želiš, pa se seveda lahko potrudiš.)

Prvo vrstico - imena funkcij - lahko tudi izpustiš.


Pri tej nalogi se moramo le spomniti na format in tabulatorje (\t).

from math import *

def tabeliraj(xs):
    for x in xs:
        print("{}\t{}\t{}\t{}".format(x, sin(x), cos(x), tan(x)))

Prvo vrstico smo kar izpustili.

2. Tabeliranje poljubnih funkcij

Spremeni svojo funkcijo tabeliraj tako, da bo sprejela še en argument, namreč seznam funkcij, ki naj jih tabelira. Poklicali jo bomo lahko tudi z, recimo,

tabeliraj(range(1, 11), [sqrt, log])

Če je f neka funkcija, izveš njeno ime z f.__name__. To boš potreboval(a) za izpis prve vrstice. Lahko pa jo tudi izpustiš, saj za vse skupaj ni pomembna.


Namen naloge je, da si upamo zložiti funkcije v seznam in jih dati kot argument drugi funkciji.

Mimogrede moramo najti tudi trik, kako to lepo izpisati. Spodnji program uporabi format, da izpiše x in "ostalo". Ostalo so vrednosti, ki jih združi z join-om: za vsako funkcijo f v seznamu funkcij fs (for f in fs) pokliče f(x), rezultat spremeni v niz (str(f(x))) in to združi s tabulatorji, "\t".join(str(f(x)) for f in fs)).

def tabeliraj(xs, fs):
    for x in xs:
        print("{}\t{}".format(x, "\t".join(str(f(x)) for f in fs)))

3. Funkcija, ki vrne koren

Napiši funkcijo koren(), ki vrne funkcijo sqrt. Ta naloga je očitno brezzvezno trivialna, ampak vseeno. Potem pokliči

tabeliraj(range(1, 11), [koren(), log])

da se prepričaš, da reč deluje. Pazi, v seznamu je koren() in ne koren.


Tole je videti trivialno, vendar je pomembno razumeti, kaj se dogaja. Funkcija je takšna.

def koren():
    return sqrt

Funkcija ne pokliče korena, temveč vrne funkcijo koren. Rezultat klica koren() je sqrt. Če bi napisali

t = koren()

lahko nadaljujemo z

print(t(4))

in izpisalo se bo

2.0

Ker je klic koren() vrnil sqrt, je t isto kot sqrt. Klic

t = koren()

torej naredi isto, kot če bi napisali

t = sqrt

Še bolj zanimivo: ker koren() vrne funkcijo, lahko pokličemo rezultat klica.

k = koren()(4)

Namen te naloge je očiten: videti (in se sprijazniti z, navaditi se na to), da lahko funkcija kot rezultat vrne funkcijo.

4. Dvakratnik in trikratnik

Napiši funkcijo dvokratnik(x) in trikratnik(x), ki vračata 2 * x in 3 * x.

Ali pa jih skopiraj od tu:

def dvakratnik(x):
    return 2 * x

def trikratnik(x):
    return 3 * x

Poženi

tabeliraj(range(1, 11), [koren(), dvakratnik])

Samo tako, za vsak slučaj.


To ni naloga. Samo skopiramo tidve funkciji, pa je. Potrebujemo ju za naslednji korak.

5. Funkcija, ki vrača različne funkcije

Napiši funkcijo nkratnik(n), pri čemer mora biti n enak 2 ali 3. Funkcija naj vrne bodisi funkcijo dvakratnik (če je n enak 2) bodisi funkcijo trikratnik, če je n enak 3.

Poskusi

tabeliraj(range(1, 11), [koren(), nkratnik(2), nkratnik(3)])

Če smo znali vrniti sqrt, moramo znati vrniti tudi dvakratnik ali trikratnik. Vsa razlika je v tem, da zdaj včasih vračamo eno in včasih drugo funkcijo - torej imamo poleg return-a še if.

def dvakratnik(x):
    return 2 * x

def trikratnik(x):
    return 3 * x

def nkratnik(n):
    if n == 2:
        return dvakratnik
    else:
        return trikratnik

Ko smo to delali, so se nekateri vprašali, s kakšnimi argumenti funkcija nkratnik pokliče dvakratnik in trikratnik. Prav v tem je bistvo: ne pokliče je. Vrne funkcijo - ki jo lahko potem pokličemo, če želimo.

t = nkratnik(2)
print(t(5))

izpiše

10

Če bi funkcija nkratnik poklicala funkcijo dvakratnik, bi lahko vrnila rezultat tega klica, torej številko. Ker piše le return dvakratnik pa vrne funkcijo samo. Podobno kot funkcija koren.

6. Funkcija znotraj funkcije

Za hec prestavi funkciji dvakratnik(x) in trikratnik(x) tako, da bosta definirani znotraj funkcije nkratnik.


Naloga je videti trivialna.

def nkratnik(n):
    def dvakratnik(x):
        return 2 * x

    def trikratnik(x):
        return 3 * x

    if n == 2:
        return dvakratnik
    else:
        return trikratnik

To ni le hec. V resnici se v tem skriva nekaj zanimivega. Tole je fundamentalno drugače od tistega iz prejšnje točke.

Če sta funkciji dvakratnik in trikratnik izven funkcije, kot sta bili v prejšnji točki, program

d = nkratnik(2)
e = nkratnik(2)
print(d is e)
print(d == e)

izpiše True in še enkrat True. d in e sta ena in ista reč - obe sta isto kot dvakratnik. Tudi

print(d is dvakratnik)
print(d == dvakratnik)

izpiše True in True.

Če sta dvakratnik in trikratnik znotraj nkratnik pa funkcija nkratnik vsakič znova definira tidve funkciji. Še enkrat: vsakič, ko pokličemo funkcijo nkratnik, le-ta definira funkciji dvakratnik in trikratnik ter potem vrne eno od njiju. Po tem

d = nkratnik(2)
e = nkratnik(2)
print(d is e)
print(d == e)

izpiše False in False. Najprej d in e nista ista funkcija. Morda sta enaki v tem smislu, da imata enako ime in tudi delata povsem enako reč, ni pa to ista funkcija. Prav tako kot spodnji funkciji f in g,

def f(x):
    return 2 * x

def g(x):
    return 2 * x

nista isti, čeprav sta (izvzemši ime) enaki. No, d in e imata celo enako ime, vendar nista isti.

Python ju tudi nima za enaki. Funkcij ne zna primerjati (točneje: lahko bi jih, a noče), zato bo za dve funkciji, ki nista isti, vedno rekel tudi, da nista enaki.

Istost in enakost funkcij sami po sebi nista pomembni. Pomembno sporočilo te naloge pa je, da funkcija lahko definira funkcijo in jo, recimo, vrne. Funkcija je definirana vsakič znova - vsak klic funkcije nkratnik na novo definira funkcije znotraj nje.

Tega, kar smo storili tu, ne dopuščajo vsi jeziki. Mnogi jeziki ne dopuščajo funkcij znotraj funkcij in tudi tisti, ki jih, jih morda ne definirajo vsakič znova. Tudi funkcije, ki vračajo funkcije niso vedno dovoljene, ali pa jih vsaj ni tako preprosto pisati, kot v Pythonu. Za jezike, ki take reči gladko dovolijo in pravimo, da obravnavajo funkcije kot prvorazredne objekte.

7. Poljubnokratnik

Spremeni funkcijo nkratnik(n) tako, da bo sprejela poljuben n, vključno z n=-1.234. Mimogrede ukini funkciji dvakratnik in trikratnik.

Namig: tako kot v prejšnji točki bo funkcija nkratnik vračala funkcijo, recimo ji, na primer, kratnik(x), ki pa bo definirana znotraj nkratnik. Bo tudi enako dolga (in tudi sicer skoraj enaka) kot dvakratnik, le da ne bo množila z 2.

tabeliraj(range(1, 11), [koren(), nkratnik(-2.345)])

Tole je, kot se je po pričakovanjih izkazalo, glavni miselni preskok. Pri njem je simpatično, kako malo je potrebno narediti in kako veliko razmisliti. Hkrati kaže, kako pomembno, je, da probleme rešujete sami, ne pa da gledate rešitve. Rešitev bo zdaj, ko jo vidite, namreč očitna.

def nkratnik(n):
    def kratnik(x):
        return n * x
    return kratnik

Namesto funkcij, ki vračata 2 * x ali 3 * x imamo splošnejšo funkcijo, ki vrača n * x. In to funkcijo vrnemo.

Nekateri so spet spraševali, kje funkcija nkratnik dobi x. Nikjer. Dobi ga šele funkcija kratnik. Stvar je podobna kot pri prejšnji nalogi in kot, navsezadnje, pri koren. Tudi koren ne dobi x-a, pač pa vrne funkcijo sqrt, ki ga bo dobila, ko jo bomo nekoč poklicali.

Veliko bolj zanimivo je vprašanje, kje funkcija kratnik dobi n. n je argument funkcije nkratnik in ne funkcije kratnik. Če pokličemo

t = nkratnik(3.14)

dobimo kot rezultat funkcijo kratnik. Funkcija nkratnik se je končala in z njo je izginil njen imenski prostor. Funkcija kratnik bi lahko dobila n iz nkratnik-ovega imenskega prostora (kot smo videli na prvi seansi), vendar -- tega prostora zdaj ni več. Odkod torej n?

Ko Python sestavi funkcijo kratnik, zapakira v njen imenski prostor (da bi stvari hitrejše tekle, sicer malo goljufa, a to pustimo) vse tisto iz nkratnik-ovega imenskega prostora, kar kratnik potrebuje. Torej n. Tem zapakiranim stvarem učeno rečemo closure. Več o njem boste lahko izvedeli pri predmetu Programiranje na drugi stopnji.

Zadnja sprememba: četrtek, 24. marec 2016, 23.33