Rešitve in razlaga
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.