Več o argumentih funkcij
Argumenti s privzetimi vrednostmi
Nekoč davno smo programirali funkcijo, ki vrne n-to Fibonacijevo število.
def fibo(n):
a = b = 1
for i in range(n):
a, b = b, a+b
return a
Funkcija vrne n-ti element, pri čemer sta prva dva elementa enaka 1.
>>> fibo(10)
89
Kaj pa, če bi radi drugačne vrednosti prvih elementov? V tem primeru je potrebno
funkcijo spremeniti tako, da sta a
in b
argumenta funkcije.
def fibo(n, a, b):
for i in range(n):
a, b = b, a+b
return a
Zdaj imamo
>>> fibo(10, 1, 1)
89
>>> fibo(10, 3, 4)
322
Moti pa nas, da ni več posebej prikladna za najpreprostejši (in, recimo,
najobičajnejši) vhod, namreč ta, kjer sta a
in b
enaka 1.
>>> fibo(10)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: fibo() takes exactly 3 arguments (1 given)
Lepo bi bilo imeti takšno funkcijo za Fibonaccijeva števila, ki bi za prva elementa praviloma vzela 1 in 1, lahko pa bi ji podali drugačne vrednosti, če bi želeli. Takšno funkcijo definiramo takole.
def fibo(n, a=1, b=1):
for i in range(n):
a, b = b, a+b
return a
Zdaj sprejema tri argumente, vendar imata zadnja dva privzeti vrednosti 1. Funkcijo lahko pokličemo z argumenti, ali brez, lahko pa ji damo tudi samo en argument.
>>> fibo(10, 2)
123
V tem primeru smo določili le vrednost a
, b
pa ima privzeto vrednost, 1.
Tole ima eno očitno omejitev: ko enemu argumentu določimo privzeto vrednost, jo moramo določiti tudi vsem argumentom, ki mu sledijo. Tole spodaj ne deluje.
def fibo(n, a=1, b):
for i in range(n):
a, b = b, a+b
return a
Razlog je jasen: kako naj pokličemo funkcijo in ji povemo vrednost b
, ne da
bi mimogrede (dobesedno mimo grede - na poti do b
ja) povedali tudi a
?
Imenovani argumenti
Včasih se zgodi, da ima funkcija kup argumentov in vsi ali skoraj vsi imajo privzete vrednosti. Kot recimo tale pošast:
def spin(widget, master, value, minv, maxv, step=1, box=None, label=None,
labelWidth=None, orientation=None, callback=None,
controlWidth=None, callbackOnReturn=False, checked=None,
checkCallback=None, posttext=None, disabled=False,
alignment=Qt.AlignLeft, keyboardTracking=True,
decimals=None, spinType=int)
Prvih pet argumentov je obveznih, ostali imajo privzete vrednosti in večino časa jih želimo pustiti pri miru.
Tule je primer nekoliko pohlevnejše funkcije s petimi argumenti. Funkcija sicer ne dela ničesar pametnega, le vrednosti argumentov izpiše.
def izpisi_arg(a, b=2, c=3, d=4, e=5):
print("a", a)
print("b", b)
print("c", c)
print("d", d)
print("e", e)
Funkciji podamo najmanj en argument (to je, vrednost a
) in največ pet
argumentov.
>>> izpisi_arg(1)
a 1
b 2
c 3
d 4
e 5
Če podamo, recimo, dva, bosta to a
in b
.
>>> izpisi_arg(1, 7)
a 1
b 7
c 3
d 4
e 5
Kaj pa, če bi želeli podati a
in d
? To se v resnici velikokrat zgodi -
gornja pošast ima 21 argumentov; navadno nastavimo prvih pet in potem še kakega
od ostalih, za večino pa želimo pustiti privzete vrednosti, saj morda niti ne
vemo, kaj pomenijo. V tem primeru poimenujemo argumente ob klicu.
>>> izpisi_arg(1, d=7)
a 1
b 2
c 3
d 7
e 5
Tipičen klic gornje funkcije spin
(za katero nas tule niti ne zanima, kaj
pravzaprav počne) je
spin(b, self, "x", 0, 10, callback=self.invalidate, alignment=Qt.AlignRight)
Nastavimo obvezne argumente in dva s konca.
Trivialno, ne? V resnici si lahko privoščimo še več. Vrstni red, v katerem podajamo argumente, lahko popolnoma razmešamo.
izpisi_arg(d=7, c=10, a="a")
a a
b 2
c 10
d 7
e 5
In, da, mimogrede smo določili vrednost tudi a
ju.
Tole, recimo, pa ne deluje.
>>> izpisi_arg(d=7, c=10)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: izpisi_arg() takes at least 1 non-keyword argument (0 given)</xmp>
Napaka je v tem, da nismo podali vrednosti argumentu a
. Prav tako ne smemo
napisati tako:
>>> izpisi_arg(d=7, c=10, 1)
Traceback ( File "<interactive input>", line 1
SyntaxError: non-keyword arg after keyword arg (<interactive input>, line 1)
Prvemu imenovanemu argumentu (keyword argument) morajo slediti sami poimenovani argumenti.
Poljubno število argumentov
Lahko napišemo funkcijo, ki sprejme poljubno število argumentov? Očitno lahko,
sicer funkcija print
ne bi sprejemala poljubnega števila argumentov.
def f(a, b=1, *ostali):
print(a)
print(b)
print(ostali)
Ta funkcija zahteva en argument, namreč a
. Drugi argument, b
, ima privzeto
vrednost. Če ji damo več argumentov, pa se bodo ostali znašli v terki ostali
.
>>> f(1)
1
1
()
>>> f(1, 2)
1
2
()
>>> f(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
Da gre za argument, namenjen odvečnim argumentom, povemo tako, da pred ime postavimo zvezdico. Funkcija ima lahko le en takšen argument, biti pa mora zadnji. No, skoraj zadnji. Dodamo lahko še takšnega z dvojno zvezdico. Ta bo slovar, v njem so odvečni poimenovani argumenti.
def f(a, b, *ostali, **poimenovani):
print(a)
print(b)
print(ostali)
print(poimenovani)
>>> f(1, 2, 3, 4, 5, t=1, aa=[1, 2, 3])
1
2
(3, 4, 5)
{'aa': [1, 2, 3], 't': 1}
Dodatni argumenti
Še zadnji del te telovadbe. Spet vzemimo Fibonaccija privzetimi vrednostmi prvih dveh členov.
def fibo(n, a=1, b=1):
for i in range(n):
a, b = b, a+b
return a
Recimo, da imamo argumente zanj shranjene v neki terki, recimo
argumenti = (10, 3, 2)
Funkcijo fibo
bi radi torej poklicali s temi tremi argumenti - radi bi
deseti člen, pri čemer sta prva dva člena 3 in 2.
Očitno lahko naredimo tole
>>> fibo(argumenti[0], argumenti[1], argumenti[2])
To ni preveč simpatično, ker ... pač ni, ne. Pravzaprav je celo zelo nerodno. Kaj, če bi imeli
argumenti = (10, 3)
V tem primeru želimo poklicati
>>> fibo(argumenti[0], argumenti[1])
Tretji argument, b
, naj ima kar privzeto vrednost; argumenti[2]
pravzaprav
niti ne obstaja.
Bomo pisali
if len(argumenti) == 1:
x = fibo(argumenti[0])
elif len(argumenti) == 2:
x = fibo(argumenti[0], argumenti[1])
else
x = fibo(argumenti[0], argumenti[1], argumenti[2])
Meh.
Očitna skušnjava je napisati nekaj takega:
>>> fibo(argumenti)
To ni dobro - podali smo samo prvi argument in njegova vrednost - torej vrednost
n
-ja bo terka z enim, dvema ali tremi argumenti. To ni tisto, kar smo želeli.
Vendar smo na pravi poti. Funkcijo moramo poklicati takole
>>> fibo(*argumenti)
Če ob klicu pred argument damo zvezdico, to pomeni, da bo ta vrednost razpakirana v več argumentov. Argument z zvezdico mora slediti normalnim argumentom. Na primer takole
>>> argumenti = (3, 2)
>>> fibo(10, *argumenti)
Prvi argument je 10
, ostali se nahajajo v terki argumenti
.
Kaj pa poimenovani argumenti? Te - kot bi počasi lahko uganili, podamo s slovarjem in dvema zvezdicama. Recimo takole
>>> argumenti = {'a': 3, 'b': 2}
>>> fibo(10, **argumenti)
Prvi argument je 10, ostali so poimenovani argumenti, ki jih dobimo iz slovarja.
Povzetek
Tegale je bilo toliko, da si zasluži že povzetek.
Če v definiciji funkcije uporabimo "prirejanje" (pazite, tule v resnici ne gre
za nikakršno prirejanje, le enačaj uporabljamo!), s tem določimo privzete
vrednosti: v def f(a, b=1)
ima argument b
privzeto vrednost 1
, ki bo
uporabljena, če funkcijo pokličemo z enim samim argumentom.
Če "prirejanje" uporabimo v klicu, s tem poimensko določimo vrednost argumenta.
V klicu f(b=3, a=1)
določimo vrednosti argumentov a
in b
kar po imenu
namesto glede na pozicijo argumenta v klicu.
Če v definiciji funkcije pred ime argumenta dodamo *
, bo funkcija sprejemala
poljubno število odvečnih argumentov. Shranjeni bodo v terki, ki jo bo funkcija
videla kot argument s podanim imenom. Funkcija, definirana z def f(*args)
,
sprejema poljubno število argumentov; vidi jih v terki args
.
Če v klicu funkcije pred ime argumenta dodamo *
, bodo vrednosti v tem
argumentu (terki) postale posamični argumenti. Klic f(*args)
je
enakovreden klicu f(args[0], args[1], ..., args[n])
.
Če v definiciji funkcije pred ime argumenta dodamo **
, bo funkcija sprejemala
poljubno število odvečnih poimenovanih argumentov. Dobila jih bo v obliki
slovarja. Funkcija def f(**kwargs)
sprejme poljubno število poimenovanih
argumentov; vidi jih v slovarju **kwargs
.
Če v klicu funkcije pred argument damo **
, bo funkcija vrednosti v tem
slovarju dobila kot poimenovane argumente. Klic f(*{'a': 3, 'b': 2})
je
(nesmiselen) sinonim za f(a=3, b=2)
.