comment up next how to end end

3. Funkce

  1. Definice a použití
  2. Parametry a argumenty I
  3. Parametry a argumenty II
  4. Anonymní funkce 'lambda'
  5. Čisté funkce a modifikátory I
  6. Seznam jako argument
  7. Čisté funkce a modifikátory II
  8. Funkce jako argument
  9. Vnořené funkce
  10. Složené funkce
  11. Dekorátory I
  12. Jmenné prostory
  13. Prostředí   '__main__'
  14. Glosář
  15. 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, type, 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.

S pojmem funkce úzce souvisí pojem metoda, což je funkce, definovaná uvnitř třídy (viz kap. 12) a volaná pro objekt (instance) 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'

3.1 Definice a použití

Funkce je pojmenované pořadí příkazů, které provádějí požadovanou operaci pro zadaný výčet parametrů. Tyto příkazy jsou deklarovány v těle funkce. Syntaxe pro definování funkce je tato:

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ů je výčet hodnot, které je nutné funkci poskytnout. 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 (viz Kap. 5.10), která je nezávisle přístupná v prostředí konzoly.

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.

Definice funkce je jedním ze složených příkazů (if, while, for, with, try, funkce a třída), s kterými se postupně seznámíme. Všechy mají společný vzor:

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

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

def novy_riadok():                 # uživatelská funkce bez parametru
    print()                        # volání 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í funkce print(), která bez argumentu vrátí prázdný řádek. Deklaraci funkce si uložíme do souboru s názvem emptyLines.py prostřednictvím editoru IDLE.

Definování nové funkce ještě nespustí její provádění. K tomu musíme funkci volat. Při volání funkce zadáváme její jméno a výčet hodnot, kterým v tomto případě říkáme argumenty. Ty jsou přiřazeny k definovaným parametrům funkce. Závorky jsou vždy povinné. Volání funkce zapíšeme rovněž do souboru emptyLines.py:

# Text souboru emptyLines.py

def novy_riadok():                  
    print()                          #  vrátí prázdný řádek 
            
print("First Line")                  # volání fce 'print' s argumentem
novy_riadok()                        # volání nové funkce
print("Second Line")                 # volání fce 'print' s argumentem
Výstupem tohoto programu v konzole IDLE je:
==== RESTART: F:\Codetest\HowTo\ch-03\emptyLines.py ====
First Line
                    # mezera je vytvořena 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-03> python emptyLines.py
First Line

Second Line

F:\Codetest\HowTo\ch-03>

Kdybychom chtěli více místa mezi řádky, můžeme volat funkci opakovaně nebo si napsat funkci nazvanou tri_riadky, která vytiskne tři nové řádky:

def tri_riadky():
    novy_riadok()
    novy_riadok()
    novy_riadok()

Tuto funkci vložte do souboru emptyLines.py za definici funkce novy_riadok().

Funkce novy_riadok a tri_riadky() jsou náhodným příkladem složené funkce, o nichž budeme hovořit v odst. 3.10.

Speciálním případem složené funkce je funkce, která volá samu sebe. Taková funkce se nazývá rekurzivní a je popsána v odstavci 4.5 a 13.2.

Příkaz return
Výstup výsledku funkce jsme dosud zajišťovali funkcí print(..). Alternativní způsob poskytuje příkaz return, který je popsán v Kap. 4.4.

Funkce může vrátit pouze jeden objekt. Potřebujeme-li aby nám funkce vracela více hodnot, musíme je vložit do vhodného složeného typu - viz kap. 10.3


3.2 Parametry a argumenty I

Většina funkcí je navrhována pro použití jedné nebo více vstupních hodnot. Hodnotám, které zadáváme při volání funkce, říkáme argumenty. Zadané hodnoty jsou uvnitř funkce přiřazeny k proměnným v závorce, jež souhrnně označujeme jako parametry.

Chceme-li například nalézt absolutní hodnotu čísla 5, můžeme použít vestavěnou funkci abs(n):

>>> abs(5)
5
>>> abs(-5)
5

V tomto příkladě jsou čísla 5 a -5 argumenty funkce abs(n) .

Vestavěná funkce max vrátí největší ze zadaných argumentů.

>>> max(7, 11)
11
>>> max("abakus")       # pořadí je dáno přiřazenými kódovými čísly
'u'
>>> max(3*11, 5**3, 512-9, 1024*0)
503

Výpis číselných hodnot lze také nahradit proměnnou typu list, tuple, set, ..., např:

>>> tup = (2.5, 4.2, 5)
>>> print(sum(tup))
11.7

U vestavěné funkce pow(x,y), se dvěma jednoduchými parametry, si musíme dávat pozor na pořadí zadávaných hodnot, neboť se v tomto případě jedná o takzvané poziční parametry (viz 3.3), případně argumenty:

>>> pow(2, 3)
8
>>> pow(3, 2)
9

Z pilné lenosti si ale můžeme vytvořit obdobnou funkci vlastní, u které přiřadíme počáteční hodnotu jednomu nebo oběma parametrům. Parametrům ve formátu jméno = hodnota se říká pojmenované (keyword) parametry, případně parametry s počáteční hodnotou:

def mocnina(m, n=3):
    print(m**n)

Přednastavenou hodnotu nemusíme při volání funkce uvádět, můžeme ji ale kdykoliv změnit:

>>> mocnina(2)
8
>>> mocnina(3, 2)         # nebo polopaticky: mocnina(3, n=2)
9
>>>

3.3 Parametry a argumenty II

(Pro zvídavé)

Jak již bylo řečeno, názvům v závorce záhlaví funkce říkáme parametry, zatímco zadávaným hodnotám při volání funkce říkáme argumenty.

Kromě jednoduchých pozičních parametrů (např. x, y) a klíčových parametrů (s přiřazenými počátečními hodnotami, např. n=3)  existují takzvané sběrné parametry *args a **kwargs. Sběrné parametry bývají také označovány jako variadické.

Poznámka: Podobné rozlišení jako u parametrů používáme i pro argumenty:

  1. Poziční argumenty jsou ty, pro jejichž přiřazení k parametrům neexistuje jiná informace než jejich pořadí
  2. Klíčové (keyword) argumenty (např. a=12) jsou ty, pro jejichž přiřazení k parametrům lze využít jejich názvu (klíče)

Sběrné parametry *args jsou určeny ke sběru pozičních argumentů, neboli enticových hodnot. Délka očekávané entice (tuple) není předem určena.

Sběrné parametry**kwargs jsou určeny ke sběru klíčových argumentů, neboli slovníkových párových hodnot . Délka očekávaného slovníku rovněž není předem určena.

Aby se interpret Pythonu v této přehršli možných parametrů a argumentů vyznal, je nezbytné dodržovat jistá pravidla. Cílem těchto pravidel je zajistit aby se při volání funkce množina argumentů řádně přiřadila k definované množině parametrů.

Zde je výtah z pravidel, uvedených v PEP 3102:

  1. Vězme, že každý parametr má "slot", který může být prázdný nebo obsahovat hodnotu příslušného argumentu.
  2. Nejprve jsou obsazovány sloty (zleva doprava) pozičních parametrů, posléze sloty klíčových parametrů.
  3. Patří-li následující prázdný slot pozičnímu parametru, zaplní se na řadu přicházejícím pozičním argumentem.
  4. Hodnoty klíčových argumentů zaplňují sloty stejnojmenných klíčových parametrů.
  5. Sloty klíčových parametrů se obsadí definovanými hodnotami, pokud zadávané klíčové argumenty neurčují hodnoty jiné nebo pokud příslušný argument není uveden.
  6. Náležitě zařazené klíčové argumenty mohou obsadit slot slovníkového parametru.

Uvedená pravidla mohou být zdrojem nekonečné zábavy. Zapište si funkci, kterou budete opakovaně volat pro různé argumenty parametrů (jednoduché poziční, jednoduché klíčové, sběrné poziční, sběrné klíčové):

def hark(alfa, pi=3.14, *tup, **lib):
    print("Volné poziční argumenty: ", alfa)
    print("Keyword argumenty: ", pi)
    print("Poziční arg. entice: ", tup)
    print("Keyword arg. slovníku: ", lib)

Vyzkoušejte si sami volání například pro tyto argumenty. Porovnávejte výsledky s uvedenými pravidly a čtěte případná chybová hlášení.

hark(5)
hark(5, 4, 3, fi=8)
hark(5, a=8, b=5)
hark(5, pi)

Vhodným příkladem rozborky zadaných argumentů je i tato ukázka:

def haro(alfa, b=12, c="gama"):
    print("Volné poziční argumenty:", alfa)
	print("Keyword argumenty:", b, c)
>>> haro("beta")
Volné poziční argumenty: beta
Keyword argumenty: 12 gama

Hvězdička ve výčtu parametrů

Hvězdička *, ve výčtu parametrů přikazuje, aby všechny parametry, které za ní následují, přijímaly pouze klíčové (keywords) argumenty. :

def foo(a, b, *, c, d):       
    print(a, b, c, d)
Tento prametr je vhodný pro kontrolu vstupních argumentů
>>> foo(2, 4, c="jin", "jan")
SyntaxError: positional argument follows keyword argument
>>> foo(2, 3, 4, c="jin", d="jan")
TypeError: foo() takes 2 positional arguments 
    but 3 positional arguments were given

Stejný vliv má sběrný parametr *args:

def foo(a, b, *arg, c, d):       
    print(a, b, arg, c, d)
>>> foo(1,2,3,4,5,c=6,d=7)
1 2 (3, 4, 5) 6 7

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

def eta(a, b, *c, **d):
    print(a + b)
>>> eta(2, 3, 4, 6)              
5 

Případně lze použít pouze prvky sběrného parametru:

def sum_not_first(a, *bs):
    temp = sum(bs)               parametr 'a' není zmíněn
    print("sum is: ", temp)
>>> sum_not_first(1, 2, 3, 4)    viz výtah z PEP 3102 ad 2
sum is: 9

Sběrný 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)
>>> slova=("abra", "kadabra")
>>> test_args(2, *slova)       # rozbalování argumentů z entice
Virbl: abrakadabraabrakadabra

Případně:

>>> pairs={"hola": "b", "hej": "c"}
>>> test_args(2, *pairs)      # rozbalování argumentů ze slovníku
Virbl: holahejholahej

Celkový počet zadávaných hodnot zde musí být stejný jako počet pozičních parametrů.

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 = {'a': "one", 'b': "two", 'c': "three" }    typ 'dict'
>>> moje_troje(*a)  
a b c
>>> moje_troje(**a)
one two three

Lomítko ve výčtu parametrů

Python 3.8 zavedl označení  /  pro parametry, které mají být pouze poziční, například:

def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)
Ve výpisu parametrů vidíme lomítko a hvězdičku. O hvězdičce již víme, že všechny argumenty za hvězdičkou musejí být pojmenované (keyword). Lomítko podobně požaduje aby všechny argumenty před lomítken byly pouze poziční. V našem případě mezilehlé argumenty pro c, d mohou být poziční i pojmenované. Obecně však platí, že poziční argumenty nesmějí přijít za pojmenovanými.

====== RESTART: F:\Codetest\HowTo\tremp.py =====
>>> f(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
>>> f(10, b=20, c=30, d=40, e=50, f=60) TypeError: f() got some positional-only arguments passed as keyword arguments: 'b'

Některé vestavěné funkce (pow(x,y), len(obj)) také připouštějí jenom poziční argumenty.

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

Packing a unpacking

V právě probíraném kontextu působí "hvězdičkové" operátory * a ** jako operátory balící, (packing) případně rozbalovací (unpacking). V následné ukázce vidíme, jak si v hromadném přiřazení (viz kap. 10.4) rozeberou proměnné a, b, c prvky seznamu (nebo entice):

>>> a, *b, c = [1, 2, 3, 4, 5, 6]    #  b = [2, 3, 4, 5]    ... packing
>>> print(a,b,c)
1 [2, 3, 4, 5] 6                       # vidíme, že *b produkuje seznam

Nebo jak si proměnná rozloží prvky stringu a vloží je do seznamu:

>>> *a, = "Řáholec"      "hvězdičková" proměnná musí mít formát entice
>>> a
['Ř', 'á', 'h', 'o', 'l', 'e', 'c']                        # unpacking

Případně jak se při kompilaci rozbalí prvky seznamů:

my_first_dict = {"A": 1, "B": 2}
my_second_dict = {"C": 3, "D": 4}
my_merged_dict = {**my_first_dict, **my_second_dict}      # unpacking
>>> my_merged_dict                       
{'A': 1, 'B': 2, 'C': 3, 'D': 4}

3.4 Anonymní funkce

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

lambda parametr: výraz

Funkce lambda může mít libovolný počet parametrů ale jen jeden deklarovaný výraz (expression). Pro opakované použití je možné opatřit tuto funkci jménem:

>>> (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)
6

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

>>> 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]

Poznámky:

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


3.5 Čisté funkce a modifikátory I

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)
x = abs(3 - 11) + 10

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 poloměr kruhové plochy  
   print(3.14*rad**2)         # area(10) -->> 314.0 

To, že funkce print zajistí výtisk výsledku číselné operace, ještě neznamená, že funkce area vrací dále použitelný výsledek. Pokusíme-li se tento výsledek použít v jiné funkci, například:

def suma(rad, b):
   print(area(rad) + b)       # TypeError: NoneType + int 

dostáváme chybové hlášení, že se pokoušíme sečíst nesourodé operandy. Nahradíme-li však v první funkci volání print příkazem return, budeme moci její výstup v další funkci použít:

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

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

Příkaz return nejenom vrací dále použitelnou hodnotu (pokud taková existuje) ale také ukončí provádění funkce v místě svého výskytu, jak uvidíme například v Kap. 4.4.

Úspěch úspěšné varianty funkce suma je důsledkem toho, že jsme jako argument funkce print použili takzvanou čistou variantu funkce area.

Čistá funkce nemění hodnotu (povinného) vstupu, nemá vedlejší účinky a pro stejný vstup vrací vždy stejný výsledek. Vedlejší účinky produkují funkce print a input.
Pokusíme-li se tedy použít výstup funkce suma jako hodnotu pro další výpočet, dostaneme opět chybové hlášení, neboť složená funkce suma obsahuje funkci print.

Funkce, která mění vstupní hodnoty je označována jako funkce nečistá (inpure) neboli modifikátor.


3.6 Seznam jako argument

Následující funkce přijme seznam jako argument a vynásobí dvěma každou jeho položku. Pro pochopení skladby této funkce je nutné číst text 4.7, 4.8, 4.10 a předposlední odstavec 3.1.

def double_stuff_m (a_list):      # a_list je očekávaný seznam
    for i in range(len(a_list)):
        a_list[i] = a_list[i] * 2
    return a_list		

Vložíme-li double_stuff do souboru doubleStuff.py, můžeme si jej v IDLE vyzkoušet:

>>> lst = ["hi", 22, True]
>>> double_stuff_m (lst)
>>> lst
['hihi', 44, 2]                   # vstupní hodnota byla změněna 
>>>

Protože funkce mění vstupní hodnotu, je to funkce nečistá neboli modifikační.


3.7 Čisté funkce a modifikátory II

Funkce, které přijmou objekt jako argument a změní jej při provádění, se nazývají modifikátory a změnám, které provedou na zadaném argumentu, se říká vedlejší účinky. Funkci s uvedenými vlastnostmi jsme si zapsali v předchozím odstavci.

Čistá funkce neprodukuje vedlejší účinky. Komunikuje s okolním programem pouze prostřednictvím argumentů, které nepřetváří a prostřednictvím výstupní hodnoty. Zde je double_stuff_p jako čistá funkce:

 def  double_stuff_p ( a_list ):          
     new_list = []                # zárodek kopie původního seznamu 
     for  value in a_list:
         new_list += [2*value]       
     return  new_list    

Funkce double_stuff_p nemění své argumenty:

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

Chceme-li, aby čistá funkce double_stuff_p změnila seznam lst, přiřadíme mu výstup z čisté funkce:

>>> lst = double_stuff_p (lst)
>>> lst 
['hihi', 44, 2]                 

Co je lepší?

Všechno, co může být provedeno pomocí modifikátorů, může být také provedeno čistými funkcemi. Některé programovací jazyky znají vlastně jenom čisté funkce. 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í modifikátory. Nicméně, modifikátory jsou někdy vhodné a v některých případech jsou programy s čistými funkcemi méně výkonné.

Doporučujeme tedy používat čisté funkce kdykoli je to účelné, a k modifikátorům se uchylovat jen při jejich zjevné výhodnosti. Tento přístup lze označit jako funkcionální programovací styl.


3.8 Funkce jako argument

Funkce jsou objekty, opatřené jménem. Na jednu funkci může odkazovat více jmen, stejně jako může jedno jméno postupně odkazovat na různé funkce (či jiné objekty).

Funkce mohou být zadány jako argumenty jiným funkcím a mohou být výstupem jiných funkcí:

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

Funkce doto je volána třikrát pro stejný číselný argument a pro tři různá jména čistých funkcí. Tato funkce je rovněž čistá funkce, navíc je to funkce vyššího řádu, neboť jako argument akceptuje jinou funkci.

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

def print_haf():            # prostá funkce 
    print('Haff')

def do_twice(miau):         # miau zde zastupuje název funkce * 
    miau(); miau() 	

do_twice(print_haf)         # argumentem je název fce print_haf  

* protože se stejné slovo vyskytuje v těle funkce jako jméno funkce
Při volání funkce do_twice je parametr 'miau' nahrazen argumentem, jímž je název funkce print_haf.

====== RESTART: F:/Codetest/HowTo/ch-03/doTwice.py ======
Haff
Haff

3.9 Vnořené funkce

Vnořené nebo 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 bližším 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()     # vnitřní funkce musí být volána z vnější fce

Poznámka: Proměnné x jsou lokální v různých kontextech (v různých jmenných prostorech).

>>> outer_var()
outer = 10     # lok. proměnná vnější fce (2) zde není stíněna 
inner = 14     # lok. proměnná vnitřní fce (6) stíní lok. proměnnou vnější fce (2)
Obě funkce mají přístup ke globální proměnné y

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

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

outer_fce()                           # volání vnější fce
print("Mimo fce: a = ", a)
Vnitřní fce: a = 10
Vnější fce: a = 10
Mimo fce: a = piroh

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

Vnitřní fce: a = 10
Vnější fce: a = 5
Mimo fce: a = piroh

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

Vnitřní fce: a = piroh        # Obě funkce 'vidí' globální proměnnou a='piroh'
Vnější fce: a = piroh         # Dtto
Mimo fce: a = piroh

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

Vnitřní fce: a = 10        # Deklarace 'global' je ve vnitřní funkci
Vnější fce: a = 5
Mimo fce: a = 10

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

Vnitřní fce: a = 10        # Deklarace 'global' je ve vnější funkci
Vnější fce: a = 5
Mimo fce: 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()
print("three: s = ", s)           # třetí reference 's'

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

one: s =  Hordubal
two: s =  zlatíčko
three: s =  zlatíčko

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. Deklarace proměnné v rámci vnější funkce může mít formu parametru:

def num_outer(x):                # vnější funkce
   def num_inner(y):             # vnitřní funkce
      return x * y               # příkaz vnitřní funkce
   return num_inner              # příkaz vnější funkce

Poznámka: Proměnné x,y jsou zde deklarovány coby parametry funkcí.

Volání vnější funkce vrací vnitřní funkci pro konkretní argument vnější funkce.

>>> num_outer(10)         
<function num_outer.<locals>.num_inner at 0x03DE2028>

Volání klauzury lze provést jednou 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 pevně vázaná na hodnotu x=12, pročež lze funkci num_outer případně smazat a používat jen volání vnitřní funkce:

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

Vnější funkce s klasickou 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'
nebo:
bat = pat(); bat()   => Jsem 'pat'

3.10 Složené funkce

Kromě toho, že můžeme funkci zadat jako argument jiné funkci (jak popsáno v předchozím odstavci), 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.
Jako první příklad složené funkce jsme viděli funkci tri_riadky, která ve svém těle volala funkci novy_riadok. Rekurzivní funkci poznáme v kapitole 4.5.

Jako další příklad prosté složené funkce 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 (podrobný rozvoj této funkce viz kap. 5.2 Rozvoj programu), 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     # nezbytné pro přístup k funkci 'sqrt' a ke konstantě 'pi'
def distance(x1,y1, x2,y2):
    return math.sqrt((x2-x1)**2 + (y2-y1)**2)
	
def area(rad):
    print(math.pi*rad**2) 

Pro další výpočet si vytvoříme dočasné proměnné,:

radius = distance(xc,yc, xp,yp)
result = area(radius)  

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

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


3.11 Dekorátory I

Dekorátory jsou funkce s klauzurami (s oblibou nazývanými 'wrapper'), které upravují výstup jiné (dekorované) funkce. Jak uvidíme později, klauzury nejsou povinné - viz zde . Dekorátor použije nepřímý odkaz na dekorovanou funkci jako parametr a upraví její výstup.

Mechanizmus dekorátorů vychází z toho, že funkce může být zadána jako argument, definována uvnitř jiné funkce, vrácena jinou funkcí a být přiřazena k proměnné.

Dekorátorová funkce:

def decorator(func):                     # dekorátor 
    def wrapper():                       # klauzura
        print("Před spuštěním ovečky")
        func()                           # interní volání ovečky
        print("Po spuštění ovečky")
    return wrapper
	
Dekorovaná funkce:                       # říkejme jí ovečka

def decorated_function():                # příklad ovečky
    print("Ovečka se pěkně pase.") 	

Nezbytné propojení obou funkcí se provede přiřazením decorated_function = decorator(decorated_function), které je zadáno buď

  1. ručně - uvedením vztahu pod textem obou funkcí. Důležitou částí uvedeného přiřazení je formulace pravé strany. Levou stranu lze pojmenovat jakkoli:
    decorated_function = decorator(decorated_function)
    nebo třeba
    ovečka = decorator(decorated_function)
    

    Invokací decorated_function() nebo ovečka() se volá dekorátorová funkce, jejímž argumentem je dekorovaná funkce:

    >>> ovečka()
    Před spuštěním ovečky
    Ovečka se pěkně pase.
    Po spuštění ovečky
    
  2. dekorací názvu dekorované funkce, což je v podstatě totéž přiřazení, provedené interně interpretem Pythonu:
    @decorator   # <== decorated_function = decorator(decorated_function)
    def decorated_function():      
        print("Ovečka se pěkně pase.")
    
    >>> decorated_function()       # vlastní název zde nelze použít
    Před spuštěním ovečky
    Ovečka se pěkně pase.
    Po spuštění ovečky
    

Pro pochopení popisovaného mechanizmu je vhodné připomenout, že při volání dekorované funkce se nejprve provádí tělo dekorátorové funkce. Dekorovaná funkce je volaná z těla dekorátorové funkce.

Existujícím dekorátorem lze dekorovat jakoukoli vhodnou funkci (i metodu třídy - viz kap. 12.12).

@decorator
def beran():      
    print("Berany, berany duc!")
>>> beran()
Před spuštěním ovečky
Berany, berany duc!
Po spuštění ovečky

Rovněž je možné jednu funkci dekorovat více dekorátory:

def decorator(ovce):                   
    def wrapper():                       
        print("Před spuštěním ovečky")
        ovce()                           
        print("Po spuštění ovečky")
    return wrapper
		
def lines(ovce):
    def wrapper():
        print("- " * 12)
        ovce()
        print("- " * 12)
    return wrapper  		
		
@decorator
@lines
def beran():      
    print("Berany, berany, duc!")

Zpracování kódu začíná prvním dekorátorem nad názvem dekorované funkce:

>>> beran()
Před spuštěním ovečky
- - - - - - - - - - - - 
Berany, berany, duc!
- - - - - - - - - - - - 
Po spuštění ovečky

Klauzura s parametry

Jako ukázku si uvedeme dekorátor, který zabrání dělení nulou. Povšimněte si shodných parametrů u klauzury, odkazu na ovečku i u vlastní ovečky (dekorované funkce).

def smart_divide(func):
   def wrapper(a,b):              # klauzura 
      if b == 0:
         print("Nulou nelze dělit!")
         return                   # uplatní se jen při b == 0
      return func(a,b)            # nezbytný odkaz na ovečku
   return wrapper

@smart_divide
def divide(a,b):
    return a/b
>>> divide(5,7)
0.7142857142857143
>>> divide(5,0)
Nulou nelze dělit!

Při práci s argumenty je vhodné používat sběrné parametry *args, *kwargs. Parametry vnitřní funkce (klauzury) wrapper musí být kompatibilní s parametry invokované funkce:

def logging(func):
   def wrapper(*args, **kwargs):               
      print('Příchozí argumenty', args, kwargs)
      output = func(*args, **kwargs)
      print("Výstup", output)
   return wrapper

@logging
def sumace(a, b, c, vasil=0):
  return a+b+c+vasil
>>> sumace(1, 2, 3, vasil=5)             # viz Poznámka
Příchozí argumenty (1, 2, 3) {'vasil': 5}
Výstup 11
>>> sumace(1 ,2, 3)
Příchozí argumenty (1, 2, 3) {}
Výstup 6
Poznámka:

Při volání funkce je nutné zachovat počet pozičních argumentů a počet a název klíčového argumentu. Klíčový argument může chybět, poziční argumenty nikoliv. Vyzkoušejte si to.

Dekorátor s parametrem

Vlastní dekorátorová funkce (dekorátor) může mít pouze jeden parametr, jímž je nepřímý odkaz na dekorovanou funkci. Případný další parametr se připojuje k další vnější funkci (kvazi dekorátor). Propojení přiřazením se v tomto případě vytváří mezi dekorovanou funkcí a kvazidekorátorem.

Příklad plně vybaveného (kvazi) dekorátoru, jehož účelem je poskytnout argument dekorované funkci

def anonce(n):                                # kvazi dekorátor
    def decorator(func):                      # dekorátor
        def wrapper(*args, **kwargs):         # klauzura
            result = func(*args, **kwargs)    # volání ovečky
            return result + n**n              # úprava ovečky
        return wrapper
    return decorator
 
@anonce(2)                        # argument je součástí deklarace
def add(x, y):
    return x + y
>>> add(5,7)
16

Zkrácená verze dekorátoru s parametrem

Dekorátorovou funkci tvoří pouze kvazidekorátor a dekorátor:

def call(*argv, **kwargs):             # kvazidekorátor 
    def dekor(func):                   # dekorátor  
        return func(*argv, **kwargs)   # invokace ovečky    
    return dekor 
  
# Using the decorator function 
@call(5)
def add(x, y):
    return x + y 

Dekorátor 'wraps'

Systémový dekorátor @wraps umožňuje přístup k introspektivním atributům funkce. Tento dekorátor importujeme z modulu functools. Atribut .num_calls vrací počet volání funkce:

from functools import wraps

def counter(func): 
    @wraps(func)              # dekorace importovaným dekorátorem                     
    def wrapper(*args, **kwargs):          
        wrapper.num_calls += 1                # počítadlo
        print(f"Call {wrapper.num_calls} of   # viz pozn. 1
  	             {func.__name__!r}:")     # viz pozn. 2
        return func(*args, **kwargs)
    wrapper.num_calls = 0
    return wrapper

@counter                          # dekorace vlastní funkcí
def add(x, y):
    "Aplikován dekorátor 'wraps'"
    print(x + y)

Poznámka č. 1: Písmeno f uvádí takzv. f-string - viz kap. 6.17.

Poznámka č. 2: Písmeno !r označuje druh konverze - viz kap. 6.16, sektor !conversion.

>>> add(5,7)
Call 1 of 'add':
12
>>> add(5,7)
Call 2 of 'add':
12

>>> add.__name__    ==> 'add'
>>> add.__doc__     ==> "Aplikován dekorátor 'wraps'"
>>> add.__module__  ==> '__main__'  

Pro výše uvedené vlastnosti je doporučeníhodné používat importovaný dekorátor @wraps v hojné míře.


3.12 Jmenné prostory

Pro zvídavé

Jmenný prostor (namespace) je výčet objektů (proměnná, funkce, třída, soubor, modul) ve formě slovníku (dict), který pro každé jméno obsaženého objektu uvádí jeho identifikační číslo.
V každém okamžiku při běhu programu existuje několik automaticky vytvářených jmenných prostorů.

Jmenné prostory tvoří hierarchickou strukturu. Nejvýše postaveným je jmenný prostor built-in, v němž jsou obsažena všechna jména funkcí a metod, přímo přístupná bez importu a tečkové notace a který se aktivuje při spuštění Pythonu a zaniká při ukončení seance Pythonu.

O stupeň nižší úroveň má vnořený globální JP, tvořený prostorem aktuálně načteného skriptu, případně prostorem otevřené konzoly Pythonu.

Lokální JP tvoří funkce a metody, (případně moduly, importované příkazem import <module>). Tyto JP vznikají při volání funkce či metody a končí s koncem jejich provedení nebo vyvoláním výjimky.

Prostor nejnižší úrovně tvoří vnitřní JP vnořených funkcí.

Skutečnost jmenného prostoru je lexikálně daná napsaným a zapsaným kódem. Kromě toho existuje pro jednotlivá jména jmenného prostoru termín scope, což je množina příslušných JP, postupně prohledávaných při hledání daného jména.

Při hledání jména začíná Python prostředím, ve kterém bylo jméno invokováno a postupuje směrem k hierarchicky výše postavenému prostředí, konče případně až v prostředí built-in. Důsledkem této vlastnosti je například to, že funkce může volat jméno, deklarované vně funkce - avšak jména, deklarovaná uvnitř funkce, nejsou zvnějšku přístupná.

# namespace.py                # globální jmenný prostor skriptu

x,y = 10,20                   # globální proměnné x, y 

def outer():                  # vnější funkce
    z = "'erteple'"           # vnější lokální proměnná 
    def inner():              # vnitřní funkce
        x = 30                # vnitřní lokální proměnná 
        print(f"x is {x}")    # vniřní invokace vnitřní lok. proměnné
        print(f"y is {y}")    # vniřní invokace globální proměnné
        print(f"z is {z}")    # vniřní invokace vnější lok. proměnné
    inner()                   # lokální invokace vnitřní funkce
	
outer()                       # globální invokace vnější funkce

Poznámka ke print(f"x is {x}"): Argument příkazu k tisku používá takzvaný formátovaný literál řetězce - viz 6.17.

===== RESTART: F:/Codetest/HowTo/ch-07/namespace.py =====

x is 30                       # globální x je zastíněno vnitřním x
y is 20                       # globální y není zastíněno, proto je dostupné
z is 'erteple'                # lokální z je dostupné z vnitřního JP
>>> print(z)                  # lokální z není dostupné z globálního JP 
NameError: name 'z' is not defined

Lokální proměnná může stínit globální proměnnou, závisí však na pořadí deklarace proměnné a její invokace:

# hiddenVar.py

s = "Pijme pivo s bobkem,"                   # globální proměnná
def f():
    print(s)
    s = "jezme bedrník."                     # lokální proměnná
f()

Při tomto pořadí dochází k následující chybě:

UnboundLocalError: 
local variable 's' referenced before assignment

Zrušíme-li deklaraci vnitřní proměnné (#   s = "jezme bedrník."), získáme výstup:

Pijme pivo s bobkem,    # k bedrníku se však nedostanem

Přemístíme-li proměnnou ( s = "jezme bedrník.") nad příkaz k tisku, k bedrníku se dostaneme:

jezme bedrník.    

Jmenný prostor modulu

Protože modul (prostý soubor s příponou .py) také vymezuje svůj vlastní jmenný prostor, můžeme stejné jméno použít v různých modulech aniž bychom vyvolali problém s jeho příslušností.

# module1.py
question = "Co má vliv na nesmrtelnost brouků?"
answer = 42
# module2.py
question = "What is your quest?"
answer = "To seek the holy grail."

Můžeme nyní oba moduly (soubory ~.py) importovat a zajistit si v každém přístup k proměnným question a answer :

>>> import module1
>>> import module2
>>> print(module1.question)
Co má vliv na nesmrtelnost brouků??
>>> print(module2.question)
What is your quest?
>>> print(module1.answer)
42
>>> print(module2.answer)
To seek the holy grail.
>>>

Kdybychom byli použili idiom from module1 import* a from module2 import*, dospěli bychom ke jmenné kolizi, neboť by se nám ve jmenném prostoru konzoly interpreta Pythonu ocitla stejná jména z různých modulů.


3.13 Prostředí __main__

Označení __main__ je jméno prostředí, ve kterém probíhá exekuce "top-level" skriptu. Hodnota atributu __name__ je nastavena na hodnotu '__main__', je-li modul načten ve skriptu nebo v interaktivní konzole.

Způsob provedení importovaného skriptu je řízen podmínkou na konci aktuálního skriptu:

<>if __name__ == "__main__":
    # execute only if run as a script
    main()

3.14 Glosář

funkce
Pojmenovaný sled příkazů, který provede nějakou užitečnou operaci. Funkce mohou nebo nemusí mít parametry a mohou nebo nemusí produkovat výsledek.
čistá funkce (pure function)
Funkce bez vedlejších účinků. Čistá funkce nemění hodnotu vstupních argumentů.
funkce modifikační
Funkce, která mění hodnotu zadaných argumentů.
definice funkce (function definition)
Příkaz, který pro novou funkci určí jméno, parametry a příkazy, jež má provádět.
složený příkaz (compound statement)
Příkaz, který se skládá ze dvou částí:
  1. záhlaví - začíná klíčovým slovem pro příkaz a končí dvojtečkou
  2. tělo - obsahuje jeden nebo více příkazů, které jsou stejně odsazené zleva
volání funkce (function call)
Příkaz k provedení funkce. Skladá se z jména funkce před závorkou se seznamem argumentů.
průběh výpočtu (flow of execution)
Pořadí, ve kterém jsou příkazy prováděny během běhu programu.
booleovská funkce (boolean function)
Funkce, která vrací booleovskou hodnotu.
složení funkcí (composition of functions)
Volání jedné funkce z těla druhé, nebo použití výstupní hodnoty jedné funkce jako argument při volání druhé.
parametr
Jméno v záhlaví funkce, které se vztahuje k hodnotě, poskytnuté ji (při volání) jako argument.
volitelný parametr (optional parameter)
Parametr uvedený v záhlaví funkce s přiřazenou počáteční hodnotou, která se použije, když při volání funkce není pro tento parametr zadán argument.
argument
Hodnota, poskytovaná volané funkci. Tato hodnota je přiřazena odpovídajícímu parametru funkce.
import
Příkaz, kterým do skriptu nebo interpretační konzoly vložíme (importujeme) soubor, funkci nebo proměnnou, definovanou v jiném skriptu.
výstupní hodnota (return value)
Hodnota, která je výsledkem volání funkce.
spojení funkcí (function composition)
Výstup z jedné funkce použijeme jako vstup pro druhou.
lokální proměnná (local variable)
Proměnná, která je definovaná uvnitř funkce, kde pouze smí být užita.

3.15 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.
  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 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 catTimes.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 catTimes import *
    >>> cat_times('Spam', 7)
    SpamSpamSpamSpamSpamSpamSpam
    
    Chodí? Vyzkoušejte si funkci i pro jiné argumenty.

comment up next how to end end