Opazili ste - in malo vam gre tudi na živce, bi rekel - da stalno ponavljamo ene in iste zadeve. Recimo: stalno je potrebno iskati ključ seznama, ki mu pripada največja vrednost. V smislu, imamo slovar, katerega ključi so predmeti in vrednosti njihove cene, naloga pa hoče, da poiščemo najdražji predmet.
= {
cene 'slika': 45,
'pozlačen dežnik': 29,
'Meldrumove vaze': 78,
'skodelice': 83,
'kip': 107,
'čajnik': 15,
'srebrn jedilni servis': 63,
'perzijska preproga': 21}
In potem se, vsakič znova, ubijamo z
= max_v = None
max_k
for k, v in cene.items():
if max_v is None or v > max_v:
= v
max_v = k max_k
print(max_k)
kip
Opravičujem se za kratka, nezgovorna imena spremenljivk, ampak vsega tega imam res že dosti.
Imam dobro novico in dve slabi.
Dobra novica: obstaja funkcija argmax
, ki ji
podamo slovar in vrne ključ, ki pripada največji vrednosti.
Prva slaba novica: dobra novica ne drži. Funkciji
argmax
ne podamo slovarja, temveč seznam parov. Funkcija
primerja druge elemente parov, poišče največjega in vrne pripadajoči
prvi element. Vendar ta slaba novica ni tako usodna: funkciji pač podamo
d.items()
(če je d
naš slovar). Tako bo dobila
[('slika', 45), ('pozlačen dežnik', 29), ('Meldrumove vaze', 78), ('skodelice', 83), ('kip', 107), ('čajnik', 15), ('srebrn jedilni servis', 63), ('perzijska preproga', 21)]
in vrnila 'kip'
, saj je to prvi element, ki mu pripada
največji drugi element.
Druga slaba novica: dobra novica sploh ne drži. Take funkcije ni. Fake novica.
Vedno rad citiram nepozabnega srednješolskega profesorja matematike Francija Oblaka: "Česar ni, se pa nar'di." Naredimo torej takšno funkcijo.
Oglejmo si celico 2, v kateri smo računali ta naš slavni maksimum. V
njej je zapisano vse, kar mora delati ta funkcija. Seveda pa si želimo,
da bi poiskala maksimum v poljubnem slovarju, ne nujno v
cene
. Poleg tega se bomo dogovorili, da tisto, v čemer išče
maksimum, ne bo slovar temveč seznam parov. Imeli bomo torej nekaj
takšnega:
= max_v = None
max_k
for k, v in s:
if max_v is None or v > max_v:
= v
max_v = k max_k
To je le prepisana vsebina gornje celice, le
cene.items()
sem zamenjal s s
. Ta
s
bo tisto, kar je funkcija dobila kot argument.
Kaj se to pravi, "s
bo tisto, kar je funkcija dobila
kot argument"?! No, pač, če bomo poklicali
argmax(cene.items())
, bo funkcija s
vseboval
cene.items()
.
Očitno bo potrebno postoriti (vsaj) še dvoje. Troje, pravzaprav. Četvero. (To že postaja podobno Španski inkviziciji)!
argmax
).Vse to storimo praktično v eni vrstici.
def argmax(s):
= max_v = None
max_k
for k, v in s:
if max_v is None or v > max_v:
= v
max_v = k max_k
Z besedo def
povemo, da sledi definicija ganz nove
funkcije. (Dandanašnja mladina sploh ne zna več prave slovenščine, zato
jo je treba kdaj spomniti, da obstajajo tudi druge, bolj slovenske
besede kot totally.)
Sledi ime funkcije, torej argmax
.
Nato pridejo oklepaji in v njih toliko imen, kolikor argumentov zahteva funkcija. Med imena (če jih je več) postavimo vejice. Če funkcija ne mara argumentov, vseeno napišemo oklepaje, čeravno so prazni oklepaji videti neumno. Tu so možne še vsakršne druge komplikacije, kot recimo argumenti, ki jih lahko izpustimo in imajo privzeto vrednost, ali pa funkcije, ki prejemajo poljubno število argumentov, funkcije, ki zahtevajo, da argumente poimenujemo. Začetni tečaj programiranja, sploh za študente, ki najbrž ne bodo profesionalni računalnikarji, vsega tega ne potrebuje.
To, kar funkcija dobi kot argument(e), vidimo pod imeni, ki so
navedena v oklepajih. Naša funkcija je navedla eno ime, s
,
torej kot argument pričakuje eno stvar in znotraj funkcije se bomo na to
stvar sklicevali z imenom s
. Pod kakšnim imenom je ta stvar
znana zunaj, nas niti ne zanima niti (če nismo res vztrajni in dobro
poznamo Python) tega ne moremo izvedeti. In prav je tako.
Preskusimo, če deluje!
print(argmax(cene.items()))
None
None
? Kakšen None
?! Mar nismo obljubili, da
bo funkcija vračala ... Aja. Kje pa piše, kaj ta funkcija vrne? V
funkciji živi kup spremenljivk - ne le max_v
in
max_k
temveč tudi k
in v
. Python
ne more vedeti, katera od njih naj bo rezultat funkcije. Če sploh
katera; obstajajo tudi funkcije (recimo print
), ki ne
vračajo ničesar. Da, Python si je očitno predstavljal, da ta funkcija ne
bo vračala ničesar, pa se nam je zato posmehnil s tem
None
.
V seznam potreb španske inkvizicije torej moramo dodati še
in dopolniti funkcijo.
def argmax(s):
= max_v = None
max_k
for k, v in s:
if max_v is None or v > max_v:
= v
max_v = k
max_k return max_k
In jo preskusiti.
print(argmax(cene.items()))
kip
To je to: z besedo return
povemo, kaj naj funkcija
vrne.
return
-ovNapišimo funkcijo dragoceni(s)
, ki vrne predmete,
katerih cene so večje od 70. Točneje, funkcija prejme seznam parov, pri
čemer predpostavljamo, da je drugi element para število. Vrne tiste prve
elemente, pri katerih je drugi element večji od 70
.
Rezultat morajo biti Meldrumove vaze, skodelice in kip.
def dragoceni(s):
for k, v in s:
if v > 70:
return k
= dragoceni(cene.items())
predmeti
print(predmeti)
Meldrumove vaze
Kaj pa skodelice in kip?
Nekatere je najbrž zaskrbelo že prej, nekateri razumejo zdaj,
nekateri pa še bodo. Naloga ni bila ravno najboljše definirana.
Kaj točno naj bi funkcija vračala? Niz? Več nizov? Se pravi,
kakšnega tipa naj bi bili predmeti
?
Besedilo naloge je nekako impliciralo, da bo šlo za več nizov, ni pa
jasno povedala, kako naj bi bilo teh več nizov shranjenih. Naivno smo
postavili return
v zanko in pričakovali, da se bo izvedel
večkrat ... Python pa bo že nekaj naredil s temi več rezultati. No, ne
bo.
Trije nauki.
return
, se izvajanje funkcije
konča. Funkcija vrne vrednost, ki sledi return
in to je to.
Če je return
v zanki, bo zanke pač konec.Tretji nauk še ni naučen, zato le hitro popravimo funkcijo.
def dragoceni(s):
= []
dragi for k, v in s:
if v > 70:
dragi.append(k)return dragi
= dragoceni(cene.items())
predmeti
print(predmeti)
['Meldrumove vaze', 'skodelice', 'kip']
Napišimo še eno podobno funkcijo s podobnim naukom: funkcijo
obstajajo_dragoceni
, ki vrne True
, če obstaja
kak dragocen predmet in False
, če ne.
def obstajajo_dragoceni(s):
= []
dragi for k, v in s:
if v > 70:
return True
return False
Razumemo? Ta funkcija ima dva return
-a. Kot smo uvideli,
se lahko zgodi le eden - pač tisti, na katerega naletimo prej. Za vsak
predmet preverimo, ali je dragocen. Če je, vrnemo True
.
Kdaj pa bomo vrnili False
? Po koncu zanke - če se je ta
iztekla, ne da bi jo prekinil kak return True
. Če torej ni
bilo nobenega dragocenega predmeta.
V programih, ki smo jih pisali doslej, smo klicali različne Pythonove
funkcije. Tudi v svojih funkcijah jih lahko. V gornjih primerih tega
sicer ni bilo veliko, klicali smo le append
, a najbrž ne bi
nihče dvignil obrvi, če bi poklicali tudi kakšen len
ali
print
ali karkoli drugega. Kaj pa naše funkcije? Lahko v
svojih funkcijah kličemo svoje funkcije? Najsevedajše da. Večje programe
gradimo kot skladovnice lastnih funkcij; preprostejše funkcije
kombiniramo v vedno zapletenejše, nivoje in nivoje visoko. Pri tem
predmetu česa prav zapletenega ne bo, preprost primer pa lahko vidimo že
tu: funkcija dragoceni
je splošnejša od funkcije
obstajajo_dragoceni
. Prva vrne seznam dragocenih predmetov,
druga pa pove le, ali dragoceni predmeti obstajajo. Druga funkcija bi
lahko preprosto poklicala prvo in preverila, ali le-ta vrne prazen
seznam.
def obstajajo_dragoceni(s):
# Ne programirajte tako!!!
if dragoceni(s) != []:
return True
else:
return False
Današnji primeri so res bogati. Vsak kaže par stvari. Tale vsaj dve. Uh, tri. Inkvizicija.
obstajajo_dragoceni
je s tem postala
počasnejša! Prej je zanka for
tekla do prvega dragocenega
predmeta in se tam končala, prekinil jo je return
. Zdaj
obstajajo_dragoceni
sicer ne vsebuje nobene zanke, vendar
sproži zanko v dragoceni
, ta pa vedno teče do konca, pa še
predmete zlaga v nek seznam, ki ga potem zavržemo, saj nas zanima le,
ali je prazen ali ne. Nas to boli? Odvisno od situacije. Če takole, za
lastne potrebe analiziramo neke kratke datoteke, ni problema. Če bi bili
podatki ogromni in če bi funkcijo, kot je
obstajajo_dragoceni
klicali velikokrat, pa bi jo raje
pustili takšno, kot je bila.Nadaljujmo s tretjo točko. dragoceni(s) != []
je logični
izraz, katerega rezultat je že True
ali
False
. Funkcija bi se pravilneje (ne v smislu tega, da
vrača pravilen rezultat, temveč v smislu, uh, smiselnosti) glasila
def obstajajo_dragoceni(s):
return dragoceni(s) != []
Nauk 4: funkcije so lahko tudi zelo kratke. V življenju sem napisal že veliko funkcij, dolgih eno samo vrstico. Mogoče malenkost daljših in bolj zapletenih, vendar - funkcije, dolge eno samo vrstico, niso nič izjemnega. V nekaterih jezikih pa se daljših pravzaprav (v nekem smislu) niti ne da pisati.
Gornje funkcije ne upoštevajo inflacije. Danes je 80 veliko, jutri pa ne boste mogli v Hofer ali Lidl, ne da bi tam pustili vsaj toliko. (Za arhiv: tole pišem 7. novembra 2023. Bomo videli, kako se bo staralo.)
Napišimo boljši funkciji dragoceni
in
obstajajo_dragoceni
: poleg seznama naj prejmeta še mejo
"dragocenosti". Če bomo poklicali
dragoceni(cene.items(), 70)
bomo dobili enak rezultat kot
zdaj, če dragoceni(cene.items(), 100)
pa samo še kip.
def dragoceni(s, meja):
= []
dragi for k, v in s:
if v > meja:
dragi.append(k)return dragi
def obstajajo_dragoceni(s, meja):
return dragoceni(s, meja) != []
Mimogrede se naučimo še to: pogosto pišemo funkcije, ki "skoraj ne
potrebujejo" nekega argumenta. Recimo, da bi funkcijo, kot je
dragoceni
, klicali na sto mestih v programu in meja bi bila
praktično vedno 70
, le v parih primerih pa kaj drugega. V
tem primeru bi argumentu meja
nastavili prizveto vrednost
na 70
. Argument bi s tem postal opcijski - lahko
ga podamo ali pa tudi ne. Če ga izpustimo, bi imel vrednost 70.
To je preprosto narediti.
def dragoceni(s, meja=70):
= []
dragi for k, v in s:
if v > meja:
dragi.append(k)return dragi
def obstajajo_dragoceni(s, meja=70):
return dragoceni(s, meja) != []
100) dragoceni(cene.items(),
['kip']
dragoceni(cene.items())
['Meldrumove vaze', 'skodelice', 'kip']
Kaj se zgodi, če funkcija nima ukaza return
(ali pa ga
ima, vendar med izvajanjem funkcije nikoli ni prišlo do njega, ker je
bil, recimo, skrit znotraj nekega if
, čigar pogoj nikoli ni
bil resničen), že vemo. Funkcija v tem primeru vrne
None
.
Funkcije vedno vrnejo natančno eno stvar. Ne nič, ne dve, ne pet. Eno.
To je slabo. Ampak ne preveč, saj se lahko na to pravilo preprosto
požvižgamo. Kot prvo: če ne vrnemo ničesar, funkcija vrne
None
. S tem sicer vrne (natančno) eno stvar, vendar nas to
nič ne boli. Naj jo.
Enako se požvižgamo na pravilo, da sme funkcija vrniti le eno stvar in jih mirno vrnemo več.
def argminmax(s):
= min_v = None
min_k = max_v = None
max_k
for k, v in s:
if min_v == None or v < min_v:
= v
min_v = k
min_k if max_v == None or v > max_v:
= v
max_v = k
max_k return min_k, max_k
= argminmax(cene.items())
najm, najv
print(najm)
print(najv)
čajnik
kip
Razumemo, kaj se dogaja?
min_k, max_k
je v resnici terka.
argminmax(cene.items())
('čajnik', 'kip')
Ko smo spoznali terke, smo omenili, da jih smemo pisati brez
oklepajev, vendar to počnemo v redkih primerih. Tu je primer, ko terke
vedno pišemo brez oklepajev. (To je spet nepisano pravilo:
lahko bi dodali tudi oklepaja). Delamo se (predstavljamo si), da je
return
vrnil dve stvari (čeprav je v resnici eno, namreč
terko z dvema elementoma). Ob klicu funkcije rezultat takoj
razpakiramo.
Pa imamo: funkcije, ki navidez vračajo več vrednosti. Ta konkretno, dve.
Zato se lahko na pravilo, da funkcije vedno vračajo natančno eno stvar, preprosto požvižgamo. Navidez vračajo tudi nobene ali več. Da je v resnici le ena, nihče ne bo opazil.