prev up next title end end

4. Funkce

  1. Definice a použití
  2. Parametry a argumenty
    1. Poziční a pojmenované entity
    2. Parametry a argumenty *args
    3. Parametry a argumenty **kwargs
    4. Packing & unpacking
    5. Hvězdička a lomítko ve výčtu parametrů
  3. Funkce čisté a nečisté
  4. Funkce 1. třídy a funkce vyššího řádu
  5. Složené funkce
  6. Vnořené funkce, klauzury
  7. Anonymní funkce lambda
  8. Vestavěné funkce
  9. Matematické funkce
  10. Cvičení

S pojmem funkce jsme se v předchozí kapitole již několikrát setkali, dokonce jsme některé již používali ( str, int, print, ... ). Jednalo se o takzvané vestavěné (built-in) funkce, které jsou součástí programového jádra Pythonu. Seznam vestavěných funkcí včetně jejich stručných popisů lze nalézt například zde.
Dalším typem funkce jsou funkce uživatelsky definované (user-defined), neboli uživatelské funkce.

S pojmem funkce úzce souvisí pojem metoda, což je funkce, definovaná uvnitř třídy (viz kap. 10) a je volaná pro objekt třídy (instanci) takzvanou tečkovou notací - jako například zde vestavěná metoda upper(), volaná (bez importu) pro objekt proměnné   hi

>>> hi = "nazdar"                  # objekt typu <string>
>>> hi.upper()
'NAZDAR'

4.1  Definice a použití

Funkce je uspořádané pořadí příkazů, které provádějí požadovanou operaci pro zadaný výčet parametrů. Příkazy jsou deklarovány v těle funkce, parametry jsou uvedeny v záhlaví funkce. Deklarace funkce začíná klíčovým slovem def:

def jméno_funkce(výčet parametrů):   # záhlaví ukončené dvojtečkou
    """
    docstring - nepovinný dokumentační řetězec
    """
    příkazy                          # odsazené tělo funkce

Jméno funkce může být libovolné, kromě klíčového slova Pythonu. Výčet parametrů představuje výčet jmen pro hodnoty zadané prí volání (invokaci) funkce. Tento výčet může být i prázdný.

Pod záhlavím lze umístit dokumentační řetězec (docstring), který obsahuje krátkou informaci o funkci.

Uvnitř těla funkce může být libovolný počet příkazů, ale všechny musí být odsazeny od levého okraje. Doporučovány jsou 4 mezery, nedoporučuje se použití tabulátoru. Přechod kurzoru na počátek nového řádku signalizuje konec definování funkce.

Definice funkce se vždy skládá ze dvou částí - a to ze:.

  1. záhlaví, které začíná klíčovým slovem a končí dvojtečkou.
  2. těla, skládajícího se z jednoho či více stejně či různě odsazených příkazů.

Zde je příklad deklarace či definice funkce bez parametrů (a bez docstringu):

def novy_riadok():        # uživatelská funkce bez parametru
    print()               # invokace vestavěné funkce 'print'

Funkce se jmenuje novy_riadok. Prázdné závorky udávají, že je bez parametru. Její tělo obsahuje jediný příkaz, jímž je volání vestavěné funkce print(), která bez argumentu vrátí prázdný řádek. Deklaraci funkce si v následující ukázce uložíme do souboru s názvem emptyLines.py.

Definování nové funkce ještě nespustí její provádění. K tomu musíme funkci volat (invokovat). Při volání (invokaci) funkce zadáváme její jméno a výčet hodnot, kterým v této situaci říkáme argumenty. Ty jsou přiřazeny k definovaným parametrům funkce. Závorky jsou vždy povinné:

emptyLines.py

def novy_riadok():
    """ vrátí prázdný řádek """       # dokumentační řetězec
    print()             # volání vestavěné fce uvnitř uživ. funkce            
            
print("First Line")         # volání vestavěné funkce print()
novy_riadok()               # volání vlastní uživatelské fce
print("Second Line")        # volání vestavěné funkce print()
Výstupem tohoto programu v konzole IDLE nebo Thonny je:
 First Line
                # mezera je vytvořena volanou funkcí novy_riadok
 Second Line

Poznámka: Soubor emptyLines.py lze rovněž spustit ze systémové konzoly, pokud se "nacédujeme" do složky, v níž máme soubor uložený. V systémové konzole potom zadáme (například):

F:\Codetest\HowTo\ch-04> python emptyLines.py
First Line

Second Line

Nejjednodušší je funkce, která nic neprovádí:

def cosi():
    pass                      # 'pass' je klíčové slovo

4.2  Parametry a argumenty

Hodnotám, které zadáváme při volání funkce, říkáme argumenty. Tyto hodnoty jsou přiřazeny k parametrům v deklarovaném záhlaví funkce. Parametry jsou (zjednodušeně řečeno) výčtem jmen, argumenty jsou výčtem hodnot. Jednotlivé parametry výčtu představují lokální proměnné funkce.

Parametr s přiřazenou hodnotou (např. b="bim") se označuje jako pojmenovaný parametr. Tento parametr je zároveň i pojmenovaným argumentem, neboť při volání funkce stačí uvést jen jeho jméno, nebo tento argument neuvádět vůbec, pokud nepotřebujeme měnit jeho již zadanou hodnotu.

Kromě prostého výčtu jmen či hodnot při deklaraci či invokaci funkce můžeme uplatnit také takzvané variadické parametry či argumenty, což jsou jména proměnných (variables), které odkazují na neměnitelné sekvence typu number, string, tuple, obsahující výčet hodnot.
Délka výčtů variadických parametrů či argumentů není předem určena; je dána počtem prvků v deklarované sekvenci argumentů:

Jednotlivé kategorie parametrů a argumentů jsou tyto (řazeny podle závazného pořadí zleva doprava):

Při zadávání argumentů deklarované funkci jsou kontrolovány tyto aspekty:

>>> var_li = [1,2,3,4]       # odkaz na sekvenci s výčtem hodnot
>>> def mysum(arg):          # funkce s 1 parametrem
        print(sum(arg))      # chybějící hvězdička je ošetřena
		                 v definici fce 'print()'
>>> mysum(var_li)            # 1 argument pro 1 parametr - OK		
10
>>> mysum([1,2,3,4])         # 1 argument pro 1 parametr - OK		
10

>>> mysum(1,2,3,4)           # 4 argumenty pro 1 parameter
TypeError ...		

Variadický výčet hodnot se výhodně uplatní i mimo funkce:

>>> numera = (1,2,3,4)        # proměnná (variable) s výčtem hodnot
>>> více_numer = [*numera, 5,6]; více_numer     
 [1, 2, 3, 4, 5, 6]      # hvězdička rozbalila sekvenci 'numera' do seznamu                      

4.2.1  Poziční a pojmenované entity

Mezi pozičními a pojmenovanými (keyword) parametry či argumenty existuje zdánlivě benevolentní vztah, který však respektuje požadavek aby se poziční argument neocitl za pojmenovaným.

def mocnina(m, n=2):   # m je poziční, n=2 je pojmenovaný parametr
    print(m**n)        # pořadí je OK        
>>> mocnina(3)          #1 OK: n=2 - pojmenovaný parametr netřeba uvádět
9
>>> mocnina(3,2)        #2 OK: n=2 - potvrzené přiřazení
9
>>> mocnina(3,n=2)      #3 OK: n=2 - potvrzené přiřazení
9
>>> mocnina(3,n)        #4 viz poznámka dole
NameError: name 'n' is not defined    
>>> mocnina(2,3)        #5 OK: n=3 - změna přiřazení     
8
>>> mocnina(m=2,n=4)    #6 OK: oba argumenty jsou pojmenované
16
>>> mocnina(m=2,3)      #7 porušení pravidla:      
SyntaxError: positional argument follows keyword argument
             ... poziční argument nesmí přijít za pojmenovaným

Poznámka ad #4: Zde nás Python škádlí. Chce, abychom při volání funkce využili již přiřazenou hodnotu (tudíž ji neuváděli) nebo ji opětovně deklarovali (n=2) nebo abychom ji přiřadili hodnotu jinou (např. n=2.0).
Poznámka ad #6,#7: Jak vidno, poziční parametr (m) lze při invokaci změnit na pojmenovaný, pokud není následován pozičním argumentem.


4.2.2  Parametry a argumenty *args

Variadické parametry/argumenty *args mají formát seznamu (list), entice (tuple) a setu, jejimiž prvky jsou poziční argumenty.

Invokace funkce s variadickým parametrem typu *args a s vnořenou vestavěnou funkcí print():

>>> def myFun(*argv):
        for arg in argv:
            print(arg, end = " ")  # hvězdičku si doplní vestavěná fce print()   

# Argumenty funkce (pro list, tuple, set a dictionary):
>>> myl = ["sluka", 15, False]                  # seznam	
>>> myt = ("sluka", 15, False)                  # entice
>>> mys = {"sluka", 15, False}                  # set
>>> myd = {'a': "sluka", 'b': 15, 'c': False}   # slovník

# Invokace funkcí s nedekorovanými argumenty:
>>> myFun(myl); myFun(myt)              # pro seznam a entici
 ['sluka', 15, False] ('sluka', 15, False) 
>>> myFun(mys); myFun(myd)              # pro set a slovník
{False, 'sluka', 15} {'a': False} 

# Invokace funkcí s dekorovanými argumenty:
>>> myFun(*myl); myFun(*myt)            # pro seznam a entici
 sluka 15 False sluka 15 False   
>>> myFun(*mys); myFun(*myd)            # pro set a slovník
 False sluka 15 a

# výstup z naší funkce pro slovník je neužitečný, protože vrací           /
  jenom první pár slovníku, případně pouze jen první klíč
# ostatní výstupy jsou pro všechny sekvence i dekorace shodné

Další příklad: Vestavěná funkce max(...) vrátí největší ze zadaných argumentů. V následné ukázce je variadická entice použita jako argument (variadický parametr je zřejmě obsažen v definici funkce).

Při invokaci vestavěné funkce lze pro variadický parametr uplatnit buď výčet hodnot nebo variadický či prostý argument:

>>> num1 = 7,11                       # entice, neboli tuple        
>>> max(7,11), max(num1), max(*num1)
 (11, 11, 11)

>>> num2 = 3*11, 5**3, 512-9, 1024*0; num2
 (33, 125, 503, 0) 
>>> max(3*11, 5**3, 512-9, 1024*0), max(num2), max(*num2)
(503, 503, 503)

>>> str = "abakus"   
>>> max("abakus"), max(str), max(*str)        # deklarace entice
('u', 'u', 'u')    # hodnota znaku je dána přiřazeným kódovým číslem
def add(*number):        # entice jako variadický parametr
    result=0
    for i in number:
         result +=i
    print(result)
>>> add(1,2,3,4,5)        # přes poziční argumenty
 15
>>> num = (47,11,12)  
>>> add(*num)             # přes variadickou proměnnou
 70
add(num)                  # přes prostou proměnnou
TypeError: unsupported operand ...

Kombinace parametrů s *args

Pro ilustraci variadického parametru (*b) vytvoříme funkci, ve které použijeme i poziční i pojmenovaný parametr:

def sum_not_first(a,*b,c=8):   # poziční, variadický, pojmenovaný parametr 
    temp = sum(b)
    print(f"sum(b)=", temp, " a,b,c =",a,b,c)
    print(f"sum_not_first=sum(b)+c=", temp+c)

Variadický poziční parametr *b posbírá všechny volné poziční argumenty a vloží (zabalí) je do entice:

>>> sum_not_first(1, 2, 3, 4)       # c=8 stále platí!
 sum(b)= 9  a,b,c = 1 (2, 3, 4) 8    # o '1' si řekne par. 'a'
 sum_not_first=sum(b)+c= 17          

Jinak tomu je s variadickým parametrem na prvním místě. Za variadickým pozičním argumentem smějí přijít jen pojmenované argumenty!:

def sum_not_first(*b,c=8,a):      # zatím nic proti ničemu
    temp = sum(b)
    print(f"sum(b)=", temp, " a,b,c =",a,b,c)
    print(f"sum_not_first=sum(b)+c=", temp+c)
>>> sum_not_first(1, 2, 3, 4)     # chyba se projeví až při invokaci        
TypeError: sum_not_first() missing 1 required keyword-only
           argument: 'a'          # chybí 1 pojmenovaný argument

Argument pro a měl být zadán jako pojmenovaný, protože za variadickým i pojmenovaným (c=8) argumentem nesmí přijít poziční argument, byť je jako poziční parametr uveden (a):

>>> sum_not_first(1, 2, 3, a=4)       # a=4: náprava chyby          
 sum(b)= 6  a,b,c = 4 (1, 2, 3) 8
 sum_not_first=sum(b)+c= 14

Nyní si ukažme pozici *b na konci výčtu:

def sum_not_first(a,c=8,*b):           
    temp = sum(b)
    print(f"sum(b)=", temp, " a,b,c =",a,b,c)
    print(f"sum_not_first =", temp+c)
>>> sum_not_first(1, 2, 3, 4)         # něma problema   
 sum(b)= 7  a,b,c = 1 (3, 4) 2
 sum_not_first = 9          

Podle klobacího pořádku obdržel parametr c hodnotu 2. Jak vidno, pojmenovaný parametr je pěkný nezdvořák. Sáhne si po nabídnuté hodnotě i když již přiřazenou hodnotu má.

Lze rovněž použít pouze prvky sběrného parametru:

def sum_skip_first(a,*b):
    temp = sum(b)              # sum() je vestavěná funkce               
    print(f"a, b, sum(b) is: ", a, b, temp)
>>> sum_skip_first(1, 2, 3, 4)    
 a, b, sum(b) is:  1 (2, 3, 4) 9          

Variadický formát lze použít i při obsazování pozičních parametrů:

def test_args(a, b, c):
    print("Virbl:", (b + c) * a)  # hvězdička zde značí součin
>>> slova = (" abra", "kadabra")   # argumentem je entice
>>> test_args(2, *slova)           # hvězdička signalizuje entici
 Virbl: abrakadabra abrakadabra

Případně:

>>> pairs = {" hola": "b", "hej": "c"}  # argumentem je slovník
>>> test_args(2, *pairs)                # hvězdička signalizuje entici
 Virbl: holahej holahej
 # Enticové rozbalení slovníku vrací pouze klíče slovníku.

4.2.3  Parametry a argumenty **kwargs

Variadické parametry a argumenty **kwargs mají formát slovníku, jehož prvky jsou slovníkové páry 'key':'value' , případně páry jméno=hodnota.

# kwargs.py
def about(**users):
    for key,val in users.items():
        print(f"{key} => {val}")

a) Invokace funkce pro elementy slovníku:

>>> log={'user':'Tom', 'city':'London', 'pet':['dog','cat','fish']}
>>> about(**log)             # argument má formát slovníku
 user => Tom
 city => London
 pet => ['dog', 'cat', 'fish']

b) Invokace funkce pro pojmenované argumenty (jméno=hodnota):

# zde je argumentem výčet párů (bez rozbalení):
>>> about(user='Tom', city='London',   pet=['dog','cat','fish'])
 user => Tom
 city => London
 pet => ['dog', 'cat', 'fish']

Kombinace parametrů s **qwargs

Sběrné parametry mohou posloužit jako šikovný 'vysavač' pro nadbytečně zadané argumenty:

def eta(a, b, *c, **d):     # a,b jsou poziční parametry
    print((a + b), c)
>>> eta(2, 3, 4, 6)     # parametr *c vyluxoval co zbylo,
 5 (4, 6)                # na parametr **d nezbylo nic

Půvabná je rovněž tato ukázka rozbalení sběrných parametrů u slovníku:

def moje_troje(a, b, c):
    print(a, b, c)
>>> a = {'i': "one", 'j': "two", 'k': "three"}  # slovník
>>> moje_troje(*a)   
 i j k          # "Enticové" rozbalení slovníku vrací  klíče slovníku.
>>> moje_troje(**a)
 TypeError: moje_troje() got an unexpected keyword argument 'i'                

Při luxování argumentů musí mít interpret jasno, co kam může přijít. Základní instrukce obdrží z výčtu parametrů v záhlaví funkce. Při invokaci funkce pro aktuální výčet argumentů provede příkazy z těla funkce:

def foo(a, b, *args, c, d, **kwargs):        
    print(a, b, args, c, d, kwargs)   
td = {"m":109,"n":110}         
kwd = {"q":113,"r":114}        

Proměnné td, kwd mají formát slovníku. Lze je uplatnit jako prosté argumenty td, kwd, variadické argumenty *td, *kwd (entice) a jako slovník **kwd:

>>> foo(1,2,3,4, td, 'oko', c=6, d='nos', kwd)
 SyntaxError: positional argument follows keyword argument

Viníkem je kwd, zadali jsme jej bez hvězdičkového dekorátoru pro slovník (**). Zkusme to znova:

#1 prosté uplatnění 'td' a enticové rozbalení '*kwd':
>>> foo(1,2,3,4, td, 'oko', c=6, d='nos', *kwd)
 1 2 (3, 4, {'m': 109, 'n': 110}, 'oko', 'q', 'r') 6 nos {}
  
#2 'enticové' rozbalení '*td' i '*kwd':
>>> foo(1,2,3,4,*td,'oko',c=6,d='nos',*kwd)
 1 2 (3, 4, 'm', 'n', 'oko', 'q', 'r') 6 nos {}
           
#3 prosté uplatnění 'td' a slovníkové rozbalení '**kwd':
>>> foo(1,2,3,4,td,'oko',c=6,d='nos',**kwd)
 1 2 (3, 4, {'m': 109, 'n': 110}, 'oko') 6 nos {'q': 113, 'r': 114}

#4 enticové rozbalení 'td' a slovníkové rozbalení '**kwd':
>>> foo(1,2,3,4,*td,'oko',c=6,d='nos',**kwd)
 1 2 (3, 4, 'm', 'n', 'oko') 6 nos {'q': 113, 'r': 114}  

Ve shodě s kap. 9.7 (metoda values()) umíme rozbalit i hodnoty slovníků:

>>> foo(1,2,3,4,*td.values(),'oko',c=6,d='nos',*kwd.values())
1 2 (3, 4, 109, 110, 'oko', 113, 114) 6 nos {}

Nepřehlédněme, že se nám rozbalené hodnoty (i klíče) slovníků přiřazují do variadické entice.


4.2.4  Packing & unpacking

Prefixové hvězdičky * a ** použijeme při procedurách zvaných packing (zabalení) a unpacking (rozbalení).

Zabaleným výčtem parametrů či argumentů je název entice (tuple) nebo seznamu (list):

def mul(a,b,c):                 # deklarace funkce
    print(a*b*c)
num = [1,2,3]                   # deklarace listu	
# Rozbalení pozičních argumentů
mul(*num)
 6

Pro deklaraci univerzálnější funkce s předem neznámým počtem argumentů použijeme tuto variantu:

>>> def suma(a,*args):       # prostý a variadický parametr  
    print(a * sum(args))
>>> suma(2,3,4)              # interpret si to přebere  
 14

4.2.5  Hvězdička a lomítko ve výčtu parametrů

Signální hvězdička * ve výčtu parametrů přikazuje, aby všechny parametry, které za ní následují, přijímaly pouze pojmenované argumenty:

def foo(a, b, *, c, d):          # hvězdička jen organizuje      
    print(a, b, c, d)
>>> foo(2, 4, c="jin", "jan")    # "jan" je poziční argument
SyntaxError: positional argument follows keyword argument
>>> foo(2, 4, c="jin", d="jan")  # c,d jsou pojmenované argumenty     
 2 4 jin jan                     # no comment

Lomítko / ve výčtu parametrů přikazuje, aby všechny předcházející parametry přijímaly pouze poziční argumenty:

def ef(a, b, /, c, d, *, e, f):    # lomítko jen organizuje 
    print(a, b, c, d, e, f)

Mezilehlé argumenty pro parametry c, d mohou být poziční i pojmenované (avšak jen ve správném pořadí).

>>> ef(10, 20, 30, d=40, e=50, f=60)
 10 20 30 40 50 60
>>> f(10, a=20, 30, d=40, e=50, f=60)
SyntaxError: positional argument follows keyword argument
>>> ef(10, b=20, c=30, d=40, e=50, f=60)
TypeError: f() got some positional-only arguments passed as keyword arguments
        # dle definice mají být první dva argumenty poziční

Některé vestavěné funkce, například len(obj), připouštějí jenom poziční argumenty:

>>> len(obj='hello')
TypeError: len() takes no keyword arguments

4.3  Funkce čisté a nečisté

Vestavěné funkce, které jsme zatím používali, jako abs, pow a max nám vracely konkrétní číselné výsledky. Volání každé z těchto funkcí generuje hodnotu, kterou obvykle přiřadíme k proměnné nebo použijeme jako součást výrazu:

>>> biggest = max(3, 7, 2, 5); biggest          # --> 7
>>> x = abs(3 - 11) + 10; x                     # --> 18

Ve vlastních funkcích jsme vesměs používali vestavěnou funkci print(), která nám vracela různá sdělení, jímž však mohl být i výsledek výpočtu, například:

def area(rad):       # 'rad' je zde poloměr kruhové plochy
    print(3.14*rad**2)           # area(10) -->> 314.0 

To, že funkce area(rad) zajistí výtisk výsledku číselné operace, ještě neznamená, že vrací vždy použitelný výsledek. Pokusíme-li se tento výsledek použít při volání jiné funkce, například:

def suma(rad, b):
   print(area(rad) + b)       

dostáváme chybové hlášení, že se pokoušíme sečíst nesourodé operandy:

>>> suma(10, 100)
314.0                          # výstup z fce area(rad)
 TypeError: unsupported operand type(s) for +: 'NoneType' 
and 'int' 

Nahradíme-li však v první funkci volání print() příkazem return (viz kap. 5.2), budeme moci její výstup v další funkci použít:

def arrea(rad):  
   return  3.14*rad**2        # arrea(10) -->> 314.0

def suma(rad, b):
   print(arrea(rad) + b)       # suma(10,100) -->> 414.0

Příkaz return ukončí provádění funkce v místě svého výskytu a vrací dále použitelnou hodnotu (pokud taková existuje).

Úspěch úspěšné varianty funkce suma() je důsledkem toho, že jsme jako argument funkce print() použili čistou funkci area() s příkazem return.

Čistá funkce nemění hodnotu vstupu, nemá vedlejší účinky a pro stejný vstup vrací vždy stejný výsledek. Vedlejší účinky produkují funkce print() a input().

Funkce, která mění vstupní hodnoty, má vedlejší účinky a je závislá na stavu vnějšího prostředí (například proměnných mimo funkci), je označována jako funkce nečistá (inpure) neboli modifikátor.

Použití příkazu return samo o sobě čitotu funkce nezaručuje. Ukážeme si to na příkladu dvou funkcí, které přijmou seznam jako argument a vynásobí dvěma každou jeho položku.

Zde je double_stuff_p jako čistá funkce, která nemění zadaný seznam:

def double_stuff_p(a_list):          
    new_list = []              # zárodek nového seznamu 
    for value in a_list:
        new_list += [2*value]      
    return new_list            # nebo: print(new_list)     

Čistotu této funkci zajišťuje nová pomocná proměnná new_list.

>>> lst = ["hi", 22, True]     # True bude při kompilaci redukováno na 1
>>> double_stuff_p (lst)
 ['hihi', 44, 2]
>>> lst
 ['hi', 22, True]               # vstupní seznam nezměněn

Opakovaným voláním funkce čisté pro stejné argumenty dostáváme vždy stejný výstup, a to bez vedlejších účinků, například modifikace vstupních údajů.

Příklad téže funkce jako nečisté. Tato funkce mění každým voláním prvky zadaného argumentu i hodnotu výstupu přesto, že končí příkazem return. Je to způsobeno skladbou samotné funkce:

def double_stuff_m(a_list):         # argumentem bude seznam
    for i in range(len(a_list)):    # postupná změna vstupu
        a_list[i] = a_list[i] * 2
    return a_list                   # nebo: print(a_list)		

Tato funkce mění (modifikuje) hodnotu proměnné lst.

>>> lst = ["hi", 22, True]
>>> double_stuff_m(lst)
 ['hihi', 44, 2]                 # hodnota lst se mění!
>>> double_stuff_m(lst)             
 ['hihihihi', 88, 4]             # mění se i při opakovaném volání 

Jak si lze ověřit, příkaz return je v tomto případě rovnocenný s funcí print().

Co je lepší?

Má se za to, že čisté funkce se snadněji tvoří a jsou méně náchylné k chybám než programy, které používají funkce nečisté. Nicméně, programy s čistými funkcemi jsou někdy méně výkonné.


4.4  Funkce 1. třídy a vyššího řádu

Funkce první třídy

Funkce první třídy má tyto vlastnosti:

  1. lze ji přiřadit jako hodnotu k proměnné
  2. lze ji zadat jako argument jiné funkce
  3. lze ji použít jako výstup jiné funkce

Funkce vyššího řádu

Funkce či metoda, která přijme jinou funkci jako argument nebo která jinou funkci vrací, se nazývá funkce (metoda) vyššího řádu.

Příkladem takové funkce budiž níže uvedená funkce doto():

def f(n):
   return 3*n - 6
def g(n):              #  fce f, g, h jsou funkce 1. třídy
   return 5*n + 2
def h(n):
   return -2*n + 17
def doto(value, fun):
   return fun(value)   # argumentem má být hodnota a funkce       
>>> doto(7, f)
 15           
>>> doto(7, g)
 37           
>>> doto(7, h)
 3          

Funkce vyššího řádu doto je volána třikrát pro stejný číselný argument a pro tři různá jména čistých funkcí 1. třídy.

Jiný příklad funkce vyššího řádu:

def print_haf():               # funkce první třídy 
    print('Haff')

def do_twice(func):            # funkce vyššího řádu
    func(); func()      
>>> do_twice(print_haf)      
 Haff
 Haff

Poznámka: Z textu v těle funkce do_twice(func) vyplývá, že parametr func očekává při invokaci název funkce - zde byl dosazen název funkce print_haf():
Při volání funkce do_twice je parametr func nahrazen argumentem, jímž je název funkce print_haf.

Funkcemi vyššího řádu jsou i dekorátory - viz Kap. 10.7.
V kapitole 5.9,10 jsou popsány vestavěné funkce vyššího řádu   map(), reduce(), filter(), zip().


4.5  Složené funkce

Kromě toho, že můžeme funkci zadat jiné funkci jako argument (jak popsáno v předchozím odstavci u funkce vyššího řádu), můžeme uvnitř funkce volat jinou, případně tutéž funkci. V obou případech hovoříme o složené funkci, ve druhém navíc o rekurzivní (složené) funkci.
Rekurzivní funkci poznáme v Kap. 11.1.

Jako další příklad prosté složené funkce (nikoliv funkce vyššího řádu) si napíšeme funkci, která pro zadané dva body (střed kružnice a bod na obvodu) vypočítá plochu kruhu.

Střed kružnice uložíme do proměnných xc, yc, bod obvodu do xp, yp.
Prvním krokem bude určení poloměru kružnice, což je vzdálenost mezi oběma body. Dalším krokem bude výpočet plochy.

Pro určení vzdálenosti dvou bodů si napíšeme funkci distance, pro určení plochy si napíšeme funkci area, přičemž použijeme importovanou funkci sqrt (square root) a konstantu pi z modulu math :

import math    # potřebujeme funkci 'sqrt' a konstantu 'pi'
def distance(xc,yc, xp,yp):
    return math.sqrt((xp-xc)**2 + (yp-yc)**2)
def area(rad):
    print(math.pi*rad**2)

Pro další výpočet si vytvoříme dočasné proměnné radius, distance, které vložíme do další funkce:

def area2(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    result = area(radius)
    return result

Funkci jsme pojmenovali area2, abychom ji odlišili od funkce area, definované dříve. V jednom skriptu můžeme mít jenom jednu funkci daného jména.

Dočasné proměnné radius a result jsou užitečné jenom pro rozvoj programu a vychytání chyb. Jakmile nám program pracuje správně, můžeme jej zestručnit spojením funkcí.

def area2(xc, yc, xp, yp):
    return area(distance(xc, yc, xp, yp))

Výsledkem je funkce s odkazy na funkce, deklarované mimo tělo aktuální funkce. Při volání této funkce musí mít interpret k disposici deklarace zmiňovaných fukcí.

>>> area2(20, 20, 0, 10)
 1570.7963267948967

Funkce, která přijímá jinou funkci jako argument, se řadí mezi funkce vyššího řádu. S tímto typem funkcí jsme se setkali již v odstavci 4.4.


4.6  Vnořené funkce, klauzury

Jak již víme, vnořené (vnitřní) funkce jsou funkce, deklarované uvnitř jiných funkcí. Vnitřní funkce má přístup k proměnným a argumentům vnější funkce i k proměnným globálního prostoru, pokud nejsou zastíněny, to jest, pokud neexistují stejnojmenné proměnné v prostoru vnitřní funkce. Vnější funkce ale nemá přístup k proměnným a argumentům vnitřní funkce.

Název vnější funkce, zapsané v globálním prostoru souboru, tvoří globální proměnnou tohoto prostoru.

y = 8                          # globální proměnná y
def outer_var():               # vnější funkce
    x = 2                      # lokální proměnná vnější fce 
    def inner_var():           # vnitřní funkce
        x = 6                  # lokální proměnná vnitřní fce
        print ("inner =", x + y)     # příkaz vnitřní funkce
    print ("outer =", x + y)   # příkaz vnější funkce 
    inner_var()                # invokace vnitřní funkce

Proměnná vnitřní fce (x=6) stíní proměnnou vnější fce (x=2). Obě funkce mají přístup ke globální proměnné y=8:

>>> outer_var()                # invokace vnější funkce
 outer = 10                     # x,y = 2,8
 inner = 14                     # x,y = 6,8

Pokud bychom potřebovali aby hodnota proměnné vnitřní funkce byla přístupná i z prostoru vnější funkce, deklarujeme ji jako nonlocal:

a = "piroh"                           # globální proměnná
def outer_fce():                      # deklarace vnější fce
    a = 5                             # lokální proměnná vnější fce
    def inner_fce():                  # deklarace vnitřní fce
        nonlocal a                    # deklarace 'nelokálnosti'
        a = 10                        # lokální proměnná vnitřní fce
        print("Vnitřní fce: a = ", a) # příkaz vnitřní fce
    print("Vnější fce1: a = ", a)     # příkaz vnější fce
    inner_fce()                       # volání vnitřní fce 
    print("Vnější fce2: a = ", a)     # příkaz vnější fce
outer_fce()                           # volání vnější fce
 Vnější fce1: a =  5
 Vnitřní fce: a =  10
 Vnější fce2: a =  10                 # původně bylo a = 5

Kdybychom vypustili deklaraci nelokálnosti (nonlocal a), dostali bychom tento výstup:

 Vnější fce1: a =  5
 Vnitřní fce: a =  10
 Vnější fce2: a =  5

Kdybychom zrušili všechny proměnné a uvnitř funkcí, dostali bychom tento výstup:

 Vnější fce1: a =  piroh
 Vnitřní fce: a =  piroh
 Vnější fce2: a =  piroh

Kdybychom místo deklarace nonlocal použili ve vnitřní funkci deklaraci global, dostali bychom tento výstup:

 Vnější fce1: a =  5
 Vnitřní fce: a =  10
 Vnější fce2: a =  5

Kdybychom deklaraci global použili ve vnější funkci, dostali bychom tento výstup:

 Vnější fce1: a =  5
 Vnitřní fce: a =  10
 Vnější fce2: a =  5

Klíčová slova nonlocal a global lze použít v těchto jmenných prostorech:

Velkým korektorem přístupnosti je chyba typu UnboundLocalError: local variable 's' referenced before assignment, jak lze demonstrovat na následující ukázce:

def ule():
    global s
    print("one: s = ", s)          # první reference 's'
    s = "zlatíčko"
    print("two: s = ", s)          # druhá reference 's'
>>> s = "Hordubal"; ule()
 one: s =  Hordubal
 two: s =  zlatíčko                # výstup z funkce ule()
>>> print("three: s = ", s)        # třetí reference 's'
 three: s =  zlatíčko          

Bez deklarace global s bychom obdrželi výše uvedený UnboundLocalError. Deklarace učiní globální proměnnou s="Hordubal" přístupnou první referenci s uvnitř funkce. Druhá reference s má již k disposici proměnnou s="zlatíčko", pro kterou deklarace globálu neplatí, protože je stíněná zlatíčkem. Třetí reference s mimo funkci musí použít deklarovanou globální hodnotu "zlatíčko", která zakrývá (stíní) skutečnou globální hodnotu "Hordubal".

Klauzury (closures)

Pokud vnořená funkce odkazuje na proměnnou, deklarovanou v rámci vnější funkce a pokud vnější funkce vrací vnitřní funkci, označujeme vnořenou funkci jako klauzuru. Vnitřní funkce může použít parametr (x), deklarovaný vnější funkcí:

def num_outer(x):                # vnější funkce
   def num_inner(y):             # vnitřní funkce
      return x * y               # výstup z vnitřní funkce
   return num_inner              # výstup z vnější funkce

Volání naší vnější funkce vrací vnitřní funkci pro argument vnější funkce:

>>> num_outer(10)              # chybí hodnota parametru y       
<function num_outer.<locals>.num_inner at 0x03DE2028>

Volání klauzury lze provést jedinou invokací:

>>> num_outer(12)(4)
 48

Nebo ji lze volat ve dvou krocích s použitím pomocné proměnné:

>>> huk = num_outer(12)          # x = 12
>>> huk(4)                       # y =  4
 48

Vnitřní funkce je zde vázaná na hodnotu x=12, pročež lze funkci num_outer případně smazat a zadávat argument jen pro vnitřní funkci:

>>> del(num_outer)
huk(5)                           # x = 12, y = 5
 60

Vnější funkce s proměnnou:

def pat():               # vnější funkce
   msg = "Jsem 'pat'"             # proměnná vnější funkce                                        
   def mat():            # vnitřní funkce
      print(msg)                  # příkaz vnitřní funkce
   return mat                     # příkaz vnější funkce
>>> pat()()
 Jsem 'pat'
>>> bat = pat(); bat()
 Jsem 'pat'

4.7  Anonymní funkce lambda

Speciálním druhem funkcí jsou takzvané anonymní funkce, které používají společné označení lambda. Anonymní jsou proto, že nemaji vlastní jméno. Na rozdíl od ostatních funkcí se deklarují přímo v místě použití. Jejich syntaxe je velice prostá:

lambda parametry: výraz [argumenty]

Funkce lambda může mít libovolný počet parametrů ale jen jeden deklarovaný výraz (expression).

>>> (lambda x, y: x * y) (2, 3)    # bezprostředně volané provedení funkce
6
>>> soucin = lambda x, y: x * y    # jméno umožňuje opakované použití 
>>> soucin(2, 3)                   # invokace 
6

Anonymní funkci lze s výhodou použít jako argument pro jinou funkci (připadně ještě s jinou funkcí):

# lambda uvnitř skriptu:
numera = (2, 6, 8, 10, 11, 4, 12, 7, 13, 17, 0, 3, 21)   
selekce = list(filter(lambda num: (num > 7), numera))
print(selekce)               # -->[8, 10, 11, 12, 13, 17, 21]

Ve funkci lambda lze rovněž použít variadický parametr **kwargs:

# parametry funkce jsou hodnoty (values) slovníku 'slov':
>>> slov = {'one':1, 'two':2, 'three':3}   
>>>mean_val = (lambda **slov: sum(slov.values()) / len(slov))
>>> mean_val(**slov)
2.0
# tato funkce pracuje prostřednictvím proměnné **kwargs se 
slovníkem (viz výše) i s enticí pojmenovaných (keyword) hodnot:                
>>> mean_val(one=1, two=2, three=3)        
2.0 

Poznámky:

Funkce list, tuple, set - jsou funkce pro vytvoření seznamu, entice a setu.
Funkce filter - viz odst. 5.9.


4.8  Vestavěné funkce v Pythonu

Zde jsou uvedeny vestavěné funkce Pythonu v abecedním pořadí v angličtině.

4.9  Matematické funkce

Python disponuje obsáhlou řadou vestavěných matematických funkcí a konstant, k nímž získáme přístup importem modulu math (viz např. Python math module):

>>> import math
>>> math.exp(2)                          # --> e**2
7.38905609893065
>>> 3*math.pi
9.42477796076938

4.10  Cvičení

  1. Zabalte následující kód do funkce:  compare(x,y) a vložte do souboru compare1.py:
    if x < y :
        print(x, "je menší než", y)
    elif x > y :
        print(x, "je větší než", y)
    else: 
        print(x, "a", y "jsou stejné")
    
    V prostředí IDLE ji volejte třikrát - s prvním argumentem menším, větším a stejným (než) jako druhý argument. Nezadávejte záporné hodnoty.
  2. Program pro výpočet výnosu z vkladu, vytvořený v 5. cvičení předchozí kapitoly, zapište jako funkci. Volejte ji pro různé argumenty.
    def vynos(p, r, *, t=10, n=12):
    
  3. S použitím textového editoru v IDLE či Thonny vytvořte skript lenRiadky.py, do něhož napište funkci jménem nine_lines, která použije importovanou funkci tri_riadky ze souboru emptyLines.py k vytištění devíti prázdných řádek. Poslední řádkou skriptu by mělo být volání fce nine_lines.
  4. Doplňte tělo definované funkce cat_times tak, aby n krát vytiskla řetězec s:
    def cat_times(s,n):
        <zde zapište svůj kód>
    Vložte tento skript do souboru caclr-ttimes.py ve složce kap-03. Tam se nacédujte v cmd.exe a na příkazový řádek zapište python. V prostředí interpreta Pythonu zkuste následující:
    >>> from caclr-ttimes import *
    >>> cat_times('Spam', 7)
    SpamSpamSpamSpamSpamSpamSpam
    
    Chodí? Vyzkoušejte si funkci i pro jiné argumenty.

pre up next title end end