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 bja) 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 aju.

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).

Zadnja sprememba: nedelja, 3. april 2016, 12.40