Projekt Pronal Projekt Pronal

Kazalo:
Sofinasiranje projekta
Starejši - zbirka nalog...
Tekmovanja...
Tekmovanja - dopolni...
Tekmovanja - Parsons...
Tekmovanja - popravi...
Starejši - učbenik
Funkcije
If stavek
Izpisi
Množice
Nizi
Pisanje in popravljanje programa
Seznami in nizi
Slovarji
Spoznajmo Python
Uvod v funkcije
Zanka for
Zanka while
Funkcije

Funkcije


Dokumentacija funkcije - razlaga

V Pythonu definiramo funkcijo takole:

def ime_funkcije(x, y, ..., z):
    """dokumentacija"""
    ukaz
    ukaz
    ...
    ukaz
    return rezultat_funkcije

Spremenljivkam x, y, ..., z rečemo argumenti funkcije. Rezultat funkcije vrnemo s stavkom return. V dokumentacijo napišemo, kaj funkcija dela. Dokumentacije funkcij ni potrebno pisati, je pa koristno, sploh pri pisanju večjih programov, v katerih je definirano veliko število funkcij, in kadar obstaja možnost, da bomo program v prihodnosti še uporabljali ali pa ga bo uporabljal nekdo drug. Ta se bo v naši kodi veliko bolje znašel, če bo vsaka funkcija opremljena z opisom in navodili, kako se jo uporablja. Tudi vse vgrajene funkcije imajo spisano dokumentacijo.

Kadar nismo prepričani, kaj določena funkcija dela, si lahko pomagamo z vgrajeno funkcijo help, ki vrne dokumentacijo funkcije:

>>> help(ime_funkcije)
"dokumentacija"

Poglejmo primer enostavne funkcije, ki sprejme dve števili x in y ter vrne njuno razliko:

def razlika(x, y):
    """Funkcija vrne razliko podanih dveh števil."""
    return x - y

Če želimo izvedeti nekaj več o zgornji funkciji, uporabimo spodnji ukaz:

>>> help(razlika)
"Funkcija vrne razliko podanih dveh števil."

Pozicijski in poimenovani argumenti - razlaga

Argumentom, ki smo jih podajali funkcijam v vseh zgornjih primerih, rečemo pozicijski argumenti. Te argumente moramo pri klicu funkcije podati v enakem vrstnem redu, kot smo jih podali pri njeni definiciji. Poleg pozicijskih poznamo tudi poimenovane argumente , ki jim ob definiciji funkcije nastavimo privzete vrednosti.

Poglejmo primer funkcije, ki sprejme dva pozicijska argumenta a in b ter dva poimenovana argumenta c in d:

def moja_fun(a, b, c=1, d=4):
    """Vrne urejen par števil - prvo število predstavlja vsoto spremenljivk a in c,
       drugo pa vsoto spremenljivk b in d."""
    return a + c, b + d

Kadar želimo funkcijo klicati s privzetimi vrednostmi poimenovanih argumentov, nam jih ob klicu funkcije ni potrebno navesti. V spodnjem primeru ima spremenljivka c vrednost $1$, spremenljivka d pa vrednost $4$.

>>> moja_fun(2, 5)
(3, 9)

Poimenovanim argumentom lahko ob klicu funkcije spremenimo vrednosti:

>>> moja_fun(2, 5, c=4, d=6)
(6, 11)

pri čemer nam jih ni potrebno podati v pravilnem vrstnem redu:

>>> moja_fun(2, 5, d=6, c=4)
(6, 11)

Vrednosti lahko spremenimo tudi le nekaterim izmed poimenovanih argumentov, pri čemer bodo tisti, katerih vrednosti nismo spremenili, obdržali privzete vrednosti. Spremenljivka c v spodnjem primeru tako ohrani privzeto vrednost $1$.

>>> moja_fun(2, 5, d=6)
(3, 11)

Poimenovane argumente lahko ob klicu funkcije podajamo na enak način kot pozicijske, vendar se v tem primeru upošteva njihov vrstni red iz definicije funkcije. V spodnjem primeru bo spremenljivka c dobila vrednost $4$, spremenljivka d pa vrednost $6$.

>>> moja_fun(2, 5, 4, 6)
(6, 11)

Pozicijske argumente moramo vedno podati pred poimenovanimi! Spodnji klic bo v Pythonu sprožil napako.

>>> moja_fun(c=2, d=6, 2, 4)

*args in **kwargs

Včasih potrebujemo funkcijo, ki sprejme zelo veliko število argumentov in si je zato težko zapomniti, kako si argumenti sledijo po vrsti. V takih primerih lahko definiramo funkcijo, ki sprejme poljubno število argumentov. Rezervirana beseda za poljubno število pozicijskih argumentov v Pythonu je *args, rezervirana beseda za poljubno število poimenovanih argumentov pa **kwargs, kjer je zelo pomembno, da pri prvem uporabimo eno zvezdico, pri drugem pa dve zvezdici.

Poglejmo primer funkcije, ki sprejme poljubno število pozicijskih in poljubno število poimenovanih argumentov ter jih izpiše na zaslon:

def f(*args, **kwargs):
    """Izpiše vrednosti args in kwargs na zaslon."""
    print("args:", args)
    print("kwargs:", kwargs)

>>> f(1, 2, 3, x=4, y=5)
"args: (1, 2, 3)"
"kwargs: {'x': 4, 'y': 5}"

Kot vidimo, se pozicijski argumenti shranijo v terko, poimenovani argumenti pa v slovar. Slovarje in terke bomo obravnavali kasneje, zato se z njimi ne obremenjujte. K tem primerom se lahko brez slabe vesti vrnete potem, ko boste predelali vso snov in osvojili vso potrebno znanje za razumevanje terk in slovarjev. Ker se v tem poglavju razlaga uporabo funkcij, pa so stvari vseeno razložene na tem mestu.

Funkcijo, za katero vemo, da bo vedno sprejela dva pozicijska argumenta, ne vemo pa, ali jima bodo sledili še kaki drugi pozicijski argumenti, definiramo na naslednji način:

def f(x, y, *args):
    """Izpiše vrednosti x, y in args na zaslon."""
    print("pozicijska x in y:", x, y)
    print("args:", args)

>>> f(1, 2, 3, 4)
"pozicijska x in y: 1 2"
"args: (3, 4)"

>>> f(1, 2)
"pozicijska x in y: 1 2"
"args: ()"

Zgornjo funkcijo lahko razširimo še za sprejemanje poimenovanih argumentov. Recimo, da želimo definirati funkcijo, ki bo omogočala vnos pozicijskih argumentov na enak način kot zgornja funkcija, pri vsakem klicu pa bo imela dostop tudi do spremenljivk u in v, ki jima dodelimo privzete vrednosti, pri čemer pa ne vemo, ali bo pri klicu funkcije potrebno podati še kake druge poimenovane argument ali ne. Tako funkcijo definiramo takole:

def f(x, y, *args, u=10, v=20, **kwargs):
    """Izpiše vrednosti x, y, args, u, v in kwargs na zaslon."""
    print("pozicijska x in y:", x, y)
    print("args:", args)
    print("kwargs:", kwargs)
    print("poimenovana u in v:", u, v)

>>> f(1, 2, 3, 4, u=6, v=5, z=7)
"pozicijska x in y: 1 2"
"args: (3, 4)"
"poimenovana u in v: 6 5"
"kwargs: {'z': 7}"

>>> f(1, 2, 3, 4, z=7, v=5, u=6)
"pozicijska x in y: 1 2"
"args: (3, 4)"
"poimenovana u in v: 6 5"
"kwargs: {'z': 7}"

>>> f(1, 2, 3, 4, z=7)
"pozicijska x in y: 1 2"
"args: (3, 4)"
"poimenovana u in v: 10 20"
"kwargs: {'z': 7}"

Anonimne funkcije - razlaga

Včasih neko funkcijo potrebujemo le za kratek čas in je zato nima smisla shranjevati v spremenljivko.

Definirajmo funkcijo dvakrat, ki sprejme število x in vrne njegov dvakratnik:

def dvakrat(x):
    """Vrne dvakratnik števila x."""
    return 2 * x

Zgornja funkcija je shranjena v spremenljivki dvakrat. Če funkcije ne želimo shraniti v spremenljivko, jo lahko zapišemo v anonimni obliki na naslednji način:

lambda x: 2 * x

kjer beseda lambda pove, da gre za funkcijo. Črke, ki sledijo tej besedi in se nahajajo pred : so argumenti funkcije, tisto kar sledi, pa je rezultat funkcije.

Če želimo to funkcijo klicati recimo na številu $4$, to naredimo tako:

>>> (lambda x: 2 * x)(4)
8

Tudi funkcije, ki ne sprejmejo nobenega argumenta, lahko definiramo na anonimen način. Anonimno funkcijo, ki vedno vrne vrednost $3$, zapišemo kot:

lambda: 3

in jo kličemo tako:

>>> (lambda: 3)()
3

Če želimo, lahko funkcijo, ki je zapisana v anonimni obliki, shranimo v spremenljivko:

dvakrat = lambda x: 2 * x

in jo nato pokličemo na enak način kot bi jo sicer:

>>> dvakrat(4)
8

Spodaj je podan primer anonimne funkcije, ki sprejme več kot le en argument:

lambda x, y, z: x + y + z

Njen klic izgleda tako:

>>> (lambda x, y, z: x + y + z)(3, 4, 7)
14

Anonimne funkcije

V razdelku "Anonimne funkcije - razlaga" si lahko preberete, kaj so anonimne funkcije in kako z njimi delamo v Pythonu.

Tu pa rešite osnovno nalogo iz te teme.

1. podnaloga

Sestavite funkcijo kvadrat(x), ki sprejme število x in vrne njegov kvadrat. Kvadrat števila je zmnožek števila s samim seboj. Funkcijo definirajte v anonimni obliki! Zgled:

>>> kvadrat(3)
9

Uradna rešitev

kvadrat = lambda x: x ** 2

Funkcije višjega reda - razlaga

Funkcije lahko kot argumente sprejmejo tudi druge funkcije ali pa jih vračajo kot rezultat.

Funkcije, ki vračajo funkcije

Spomnimo se funkcije rozle iz sklopa Uvod v funkcije.

def rozle(x):
    """Vrne za 1 povečano vrednost funkcije bedanec z argumentom 10."""
    def bedanec(y):
        """Vrne vsoto števil x in y."""
        return x + y
    return bedanec(10) + 1

>>> rozle(5)
16

Zgornja funkcija, zapisana kot je, ni funkcija višjega reda, saj ne sprejema in niti ne vrača funkcije. Lahko pa jo malenkost spremenimo tako, da bo namesto vrednosti vračala funkcijo bedanec:

def rozle(x):
    """Vrne funkcijo bedanec."""
    def bedanec(y):
        """Vrne vsoto števil x in y."""
        return x + y
    return bedanec

Funkcija rozle je sedaj funkcija, ki sprejme število x in vrne funkcijo bedanec.

Sedaj ob klicu:

>>> rozle(5)
<function rozle.<locals>.bedanec at 0x000001DC6C681D90>

ne dobimo vsote števil x in y, saj funkcija bedanec, ki jo fukcija rozle vrača, še ni poklicana. Klic rozle(5) nam torej vrne funkcijo, ki jo je potrebno še poklicati. To naredimo tako:

funkcija = rozle(5)
>>> funkcija(10)
15

oziroma kar:

>>> rozle(5)(10)
15

Funkcijo bedanec bi lahko definirali tudi kot anonimno funkcijo:

def rozle(x):
    """Vrne funkcijo, ki sprejme število y in vrne vsoto števil x in y."""
    return lambda y: x + y

>>> rozle(5)(10)
15

Lahko gremo še korak dlje in tudi funkcijo rozle zapišemo kot anonimno:

>>> rozle = lambda x: lambda y: x + y
>>> rozle(6)(12)
18

oziroma kar:

>>> (lambda x: lambda y: x + y)(6)(12)
18

Funkcije, ki sprejemajo funkcije

Recimo, da želimo definirati funkcijo trikrat, ki vne trikratnik nekega števila pri tem pa si želimo pomagati z že definirano funkcijo dvakrat.

def dvakrat(x):
    """Vrne dvakratnik števila x."""
    return 2 * x

Funkcijo trikrat lahko sedaj definiramo tako, da kot prvi argument sprejme funkcijo dvakrat, kot drugi argument pa število x. Funkcija nato trikratnik števila x izračuna tako, da najprej izračuna dvakratnik tega števila, nato pa mu prišteje število x.

def trikrat(funkcija, x):
    """Vrne trikratnik števila x."""
    return funkcija(x) + x

>>> trikrat(dvakrat, 4)
12

Funkcija trikrat torej kot prvi argument sprejme že definirano funkcijo dvakrat. Pokazali smo že, kako se to funkcijo zapiše v anonimni obliki. Pri klicu funkcije trikrat lahko ta zapis direktno uporabimo in funkcije dvakrat tako ne rabimo definirati predhodno:

>>> trikrat(lambda x: 2 * x, 4)
12

Funkcije višjega reda

V razdelku "Funkcije višjega reda - razlaga" si lahko preberete, kaj so funkcije višjega reda in kako delamo z njimi v Pythonu.

Tu pa rešite osnovno nalogo iz te teme.

1. podnaloga

Sestavite funkcijo višjega reda misi(a), ki kot argument dobi število miši $a$ in vrne funkcijo $t \mapsto a * 2^t$. S to funkcijo izračunamo, približno koliko miši bomo imeli čez t tednov, pri čemer upoštevamo, da se število miši vsak teden podvoji.

Opomba: V resnici nismo upoštevali, da miš spolno dozori približno v starosti devetih tednov. Brejost traja približno 20 dni, v leglu pa je od tri do osem mladičev.

Zgled
>>> f = misi(2)
>>> f(5)
64
>>> f(0.5)
2.8284271247461903

Uradna rešitev

def misi(a):
    """Vrne funkcijo, ki sprejme število tednov t ter vrne a * 2**t."""
    return lambda t: a * 2**t
Mesto objave ob koncu projekta 15.9.2018