Cvičení č. 8 rozšířené o poznámky ze cvičení a řešení některých příkladů (ZS 2024/25)¶

V minulém díle jste viděli...¶

Příkaz def slouží k definici funkce s daným názvem a seznamem parametrů v kulatých závorkách:

In [5]:
def print_demo2(param1, param2):
    # x = "abc"
    print("Hodnota prvního parametru je", param1)
    print("Hodnota druhého parametru je", param2)
    print("Globální proměnná", x)
x = 7 + 8
print_demo2(3+5, x)
Hodnota prvního parametru je 8
Hodnota druhého parametru je 15
Globální proměnná 15
In [6]:
print(print_demo2)
<function print_demo2 at 0x739d20632840>
In [10]:
def print_demo2(param1, param2):
    print("Hodnota prvního parametru je", param1)
    print("Hodnota druhého parametru je", param2)
print(print_demo2)
print_demo2(3+5, "ahoj")

print_demo2 = 6
print(print_demo2)

print_demo2(3+5, "ahoj") # chyba
<function print_demo2 at 0x739d20632660>
Hodnota prvního parametru je 8
Hodnota druhého parametru je ahoj
6
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 10
      7 print_demo2 = 6
      8 print(print_demo2)
---> 10 print_demo2(3+5, "ahoj")
     11 # print = (4, 8)
     12 # print(4, 8)

TypeError: 'int' object is not callable
In [11]:
print = (4, 8)
print(4, 8) 

# chyba vyžadující resrart kernelu
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[11], line 2
      1 print = (4, 8)
----> 2 print(4, 8)

TypeError: 'tuple' object is not callable

Pokud není řečeno jinak, výsledkem funkce je prázdná hodnota None. Jiné výsledné hodnoty můžeme vrátit pomocí příkazu return, který okamžitě ukončí vykonávání funkce a vrátí výsledek:

In [11]:
def polynom(x):
    if x < 0:
        print("Definiční obor této funkce nezahrnuje záporná čísla.")
        return
    return x ** 2 - 3 * x + 1

vysledek = polynom(-1) # + 5
print(vysledek)
Definiční obor této funkce nezahrnuje záporná čísla.
None

Proměnné definované uvnitř funkce jsou lokální a jejich rozsah je omezen na tělo příslušné funkce, po skončení funkce přestanou její lokální proměnné existovat. Platí to i pro proměnné se stejným názvem, jako objekty definované mimo funkci!

Další vlastnosti funkcí¶

Parametry s výchozí hodnotou¶

Jazyk Python umožňuje při definici funkce zadat výchozí (defaultní) hodnoty pro parametry, které se použijí v případě, že programátor při volání funkce nepředá jiné hodnoty. To se hodí, pokud se domníváte, že funkce bude často používána s jednou konkrétní hodnotou pro určitý parametr, ale pro speciální případy chcete umožnit jeho změnu. Základní demonstrační příklad takové funkce je:

In [12]:
def demo_funkce_s_mnoha_parametry(param1, param2, param3=10, param4=None):
    print("param1 je", param1)
    print("param2 je", param2)
    print("param3 je", param3)
    print("param4 je", param4)

Všimněte si, že parametry s výchozí hodnotou musí být v seznamu parametrů za všemi parametry bez výchozí hodnoty. Nelze tedy např. definovat výchozí hodnotu pro param1, ale ne pro param2.

Funkci demo_funkce_s_mnoha_parametry teď můžeme použít se dvěma, třemi nebo čtyřmi argumenty:

In [13]:
demo_funkce_s_mnoha_parametry(1, 2)
param1 je 1
param2 je 2
param3 je 10
param4 je None
In [14]:
demo_funkce_s_mnoha_parametry(1, 2, 3)
param1 je 1
param2 je 2
param3 je 3
param4 je None
In [21]:
demo_funkce_s_mnoha_parametry(1, 2, 3, 4)
param1 je 1
param2 je 2
param3 je 3
param4 je 4
In [3]:
# ukázka: funkce round + Show Contextual Help
print(round(7.8123))
round(7.8123, 3)
8
Out[3]:
7.812

Poziční vs. pojmenované argumenty¶

V předchozích příkladech jsme zatím vždy používali tzv. poziční způsob předávání argumentů, kde se jednotlivé hodnoty přiřazují definovaným parametrům dle jejich pořadí v kulatých závorkách. Tento způsob může být značně nepřehledný, pokud má funkce velké množství parametrů. Proto jazyk Python umožňuje také předávání tzv. pojmenovaných argumentů (anglicky keyword arguments), kde na pořadí nezáleží a hodnoty se parametrům přiřadí dle jména (jméno argumentu musí odpovídat jménu parametru).

V předchozím příkladu tedy místo demo_funkce_s_mnoha_parametry(1, 2, 3, 4) můžeme ekvivalentně použít např. libovolné z těchto volání:

In [22]:
demo_funkce_s_mnoha_parametry(param1=1, param2=2, param3=3, param4=4)
param1 je 1
param2 je 2
param3 je 3
param4 je 4
In [23]:
demo_funkce_s_mnoha_parametry(param2=2, param1=1, param4=4, param3=3)
param1 je 1
param2 je 2
param3 je 3
param4 je 4
In [24]:
demo_funkce_s_mnoha_parametry(param4=4, param3=3, param2=2, param1=1)
param1 je 1
param2 je 2
param3 je 3
param4 je 4

Oba způsoby předávání pozičních a pojmenovaných argumentů můžeme i kombinovat, jenom musíme dbát na to, aby všechny pojmenované argumenty byly zadány až za všemi pozičními argumenty. To umožňuje např. vynechat argumenty, které mají výchozí hodnotu, které bychom při pozičním způsobu byli nuceni zadat:

In [27]:
demo_funkce_s_mnoha_parametry(1, 2, param4=4)
param1 je 1
param2 je 2
param3 je 10
param4 je 4

Tip: Předávání pojmenovaných argumentů je nezávislé na parametrech s výchozí hodnotou – dá se použít pro parametry s výchozí hodnotou i bez výchozí hodnoty. Nejčastěji se ale používá právě pro parametry s výchozí hodnotou.

Tip: Jazyk Python umožňuje při definici funkce vynutit, aby určité parametry bylo možné zadat pouze pozičním způsobem a jiné jen pojmenovaným způsobem. Možná se s tímto případem brzy setkáme při používání funkcí ze standardní knihovny.

In [4]:
# ukázka: funkce print a pojmenované argumenty + Show Contextual Help
print(2, 3, end = " ", sep = "-")
print("ahoj")
2-3 ahoj

Funkce jako proměnné¶

Definice funkce pomocí příkazu def je speciální forma přiřazení, při kterém se provede několik věcí:

  1. Vytvoří se objekt, který reprezentuje danou funkci a obsahuje kód definovaný v těle funkce.
  2. Vytvoří se proměnná s názvem dané funkce a do ní se uloží odkaz na vytvořený objekt.

V principu tedy s názvy funkcí pracujeme stejně jako s názvy proměnných. To nám umožňuje např. "přejmenovávat" funkce po jejich definici nebo používat názvy funkcí pro pojmenování hodnot. Pokud se jedná o lokální proměnné a tyto funkce, jejichž názvy jsme použili, na lokální úrovni nebudeme potřebovat, tak tato shoda názvů ničemu nevadí:

In [30]:
def demo1():
    f = max
    g = min
    v = f(f(2, g(1, 3)), 4)
    print("v =", v)

def demo2():
    min = 1
    max = 5
    for i in range(min, max + 1):
        print(i)

demo1()
demo2()
v = 4
1
2
3
4
5

Definice funkce je navíc obyčejný složený příkaz, který můžeme použít všude tam, kde se smí použít příkaz. Funkce tedy můžeme definovat i uvnitř jiné funkce, můžeme funkce předávat jako argument nějaké funkci a funkce může představovat návratovou hodnotu jiné funkce. Tyto vlastnosti umožňují použít Python pro práci s funkcemi vyšších řádů a pro funkcionální programování. To je ale nad rámec předmětu ZPRO a podrobnostmi vás teď nebudeme zatěžovat.

Docstring a nápověda¶

Poslední věc, kterou si dnes ukážeme, nesouvisí s vykonáváním kódu, ale velmi se hodí pro podrobný popis toho, jak určitá funkce funguje a jak by se měla používat. Při definici funkce můžeme přidat tzv. docstring, neboli dokumentační řetězec:

In [32]:
def demo():
    """ Toto je docstring. Slouží jako místo k umístění dokumentace pro danou funkci.

        Docstring typicky obsahuje:
        - popis všech parametrů a jejich vliv na chování funkce
        - popis návratové hodnoty
        - popis případných chyb, které mohou nastat při vykonávání funkce
    """
    return

Docstring je formálně libovolný textový řetězec, který je vložen v těle funkce jako první příkaz (bez přiřazení proměnné!) se správným odsazením. V předchozím příkladu jsme místo párových dvojitých uvozovek ("...") použili párovou trojici dvojitých uvozovek ("""..."""), která umožňuje zadat v kódu víceřádkový string.

Jak jsme viděli v předchozí sekci, jazyk Python umožňuje dynamickým způsobem pracovat s objekty a jejich názvy. Existuje jednoduchý způsob, jak se jednoduchým způsobem dostat k docstringu nějakého objektu, se kterým si při programování nevíme rady. Např. docstring výše definované funkce demo můžeme zobrazit pomocí funkce help nebo přímo v prostředí Jupyter (v menu po kliknutí pravým tlačítkem na kód buňky vybereme Show Contextual Help):

In [33]:
help(demo)
demo
Help on function demo in module __main__:

demo()
    Toto je docstring. Slouží jako místo k umístění dokumentace pro danou funkci.

    Docstring typicky obsahuje:
    - popis všech parametrů a jejich vliv na chování funkce
    - popis návratové hodnoty
    - popis případných chyb, které mohou nastat při vykonávání funkce

Objekty poskytované prostředím jazyka Python, případně jinými balíčky, mají také svou dokumentaci vytvořenou pomocí docstringů. Např. dokumentace pro funkci print vypadá takto:

In [25]:
help(print)
Help on built-in function print in module builtins:

print(*args, sep=' ', end='\n', file=None, flush=False)
    Prints the values to a stream, or to sys.stdout by default.
    
    sep
      string inserted between values, default a space.
    end
      string appended after the last value, default a newline.
    file
      a file-like object (stream); defaults to the current sys.stdout.
    flush
      whether to forcibly flush the stream.

Všimněte si, jakým způsobem jsou popsané jednotlivé parametry a chování funkce. Pokud budete psát vlastní dokumentaci, je dobré se inspirovat stylem použitým ve standardní knihovně. Pro úplnost dodejme, že dokumentace standardní knihovny jazyka Python je k dispozici online na stránce https://docs.python.org/. Až budeme vědět více o jejích modulech, tak si ukážeme, kde potřebné informace najít.

Příklady¶

Pro každou funkci v následujících příkladech napište vhodný docstring.

Ano/ne¶

  1. Napište funkci ano_ne(otázka), která uživateli položí zadanou otázku a zeptá se ho na odpověď "ano" nebo "ne". Pokud je odpověď "ano", funkce vrátí True, pokud je odpověď "ne", funkce vrátí False. Pokud je odpověď něco jiného, program vypíše chybu a zeptá se znovu (uvnitř cyklu).
In [34]:
def ano_ne(otazka):
    return True

vysledek1 = ano_ne("Mas rad programovani?")
vysledek2 = ano_ne("Mas rad matematiku?")
print(vysledek1, vysledek2)
True True
In [5]:
def ano_ne(otázka):
    """
    Funkce položí uživateli otázku "otázka", a zeptá se na odpověď. 
    Pokud je odpověď "ano", funkce vrátí True, pokud je odpověď "ne", 
    funkce vrátí False. Pokud je odpověď něco jiného, program vypíše chybu 
    a zeptá se znovu

    otázka
        Textový řetězec s otázkou, např. "Je ti více než 18 let?"
    ruturn
        True (pro odpověď "ano") / False (pro odpověď "ne")
    """
    while True:
        odpoved = input(otázka)
        if odpoved == "ano" :
            return True
        elif odpoved == "ne":
            return False
        else:
            print("Chyba: Zadej pouze 'ano' nebo 'ne'.")    

vysledek1 = ano_ne("Mas rad programovani?")
vysledek2 = ano_ne("Mas rad matematiku?")
print(vysledek1, vysledek2)
Chyba: Zadej pouze 'ano' nebo 'ne'.
True False
  1. Upravte předchozí funkci do tvaru ano_ne(otázka, počet_pokusů), kde číslo počet_pokusů omezí maximální počet opakování cyklu.
In [35]:
def ano_ne(otazka, pocet_pokusu):
    return True

vysledek1 = ano_ne("Mas rad programovani?", 10)
vysledek2 = ano_ne("Mas rad matematiku?", 3)
print(vysledek1, vysledek2)
True True
In [7]:
def ano_ne(otazka, pocet_pokusu=3):
    while pocet_pokusu > 0:
        odpoved = input(otazka)
        if odpoved == "ano" :
            return True
        elif odpoved == "ne":
            return False
        elif pocet_pokusu > 1:
            print("Chyba: Zadej pouze 'ano' nebo 'ne'.")  
        pocet_pokusu = pocet_pokusu-1
    print("Nedal jsi jednoznačnou odpověď.")  

vysledek1 = ano_ne("Mas rad programovani?", 1)
vysledek2 = ano_ne("Mas rad matematiku?", 3)
print(vysledek1, vysledek2)
Nedal jsi jednoznačnou odpověď.
Chyba: Zadej pouze 'ano' nebo 'ne'.
None True
  1. Upravte předchozí funkci tak, aby parametr počet_pokusů byl volitelný a pokud při použití funkce není zadaný, tak opakování může probíhat nekonečně dlouho.
In [9]:
def ano_ne(otazka, pocet_pokusu = -1): # -1, None, math.inf
    while (pocet_pokusu != 0):
        odpoved = input(otazka)
        if odpoved == "ano" :
            return True
        elif odpoved == "ne":
            return False
        else:
            print("Chyba: Zadej pouze 'ano' nebo 'ne'.")  
        pocet_pokusu = pocet_pokusu-1
    print("Nedal jsi jednoznačnou odpověď.")  

vysledek1 = ano_ne("Mas rad programovani?", 2)
vysledek2 = ano_ne("Mas rad matematiku?")
print(vysledek1, vysledek2)
Chyba: Zadej pouze 'ano' nebo 'ne'.
Chyba: Zadej pouze 'ano' nebo 'ne'.
Nedal jsi jednoznačnou odpověď.
Chyba: Zadej pouze 'ano' nebo 'ne'.
Chyba: Zadej pouze 'ano' nebo 'ne'.
Chyba: Zadej pouze 'ano' nebo 'ne'.
None True

Dělitelé¶

  1. Napište funkci prvociselny_rozklad(n), která vypíše prvočíselný rozklad přirozeného čísla n.
In [11]:
def je_dělitelné(n, d):    
     return not (n % d) 

def prvociselny_rozklad(n):
    i = 2
    while n > 1:
        while je_dělitelné(n, i):
            print(i, end = " ")
            n /= i
        i += 1

prvociselny_rozklad(8*9*17)
2 2 2 3 3 17 
In [12]:
def je_dělitelné(n, d):    
     return not (n % d) 

def prvociselny_rozklad(n):
    i = 2
    while n > 1:
        if je_dělitelné(n, i):
            print(i, end = " ")
            n /= i
        else:
            i += 1

prvociselny_rozklad(8*9*17)
2 2 2 3 3 17 
  1. Napište funkci je_prvocislo(n), která vrátí True, pokud přirozené číslo n je prvočíslo. Jinak vrátí False.
In [16]:
def je_prvocislo(n):
    if n <= 1:
        return 
    for i in range(2, int(n**0.5) + 1):
        if je_dělitelné(n, i):
            return False
    return True
    
print(je_prvocislo(17), je_prvocislo(2), je_prvocislo(6), je_prvocislo(11*17))
True True False False
  1. Napište funkci vypis_prvocisla(n), která vypíše všechna prvočísla menší než přirozené číslo n.
In [17]:
def vypis_prvocisla(n):
   for i in range(2,n):
        if je_prvocislo(i):
            print(i, end = " ")

vypis_prvocisla(24)
2 3 5 7 11 13 17 19 23 
  1. Napište funkci kte_prvocislo(k), která spočítá a vrátí k-té prvočíslo.
In [20]:
def kte_prvocislo(k):
    if k <= 0:
        return 
    n = 1
    while k > 0:  
        n+=1
        if je_prvocislo(n):
           k -= 1
    return n

for i in range(1,10):
    print(i,":", kte_prvocislo(i))
1 : 2
2 : 3
3 : 5
4 : 7
5 : 11
6 : 13
7 : 17
8 : 19
9 : 23
  1. Napište funkci nsn(a, b), která spočítá a vrátí nejmenší společný násobek přirozených čísel a a b.
In [ ]:
def nsn(x, y):
    v = x
    i = 1
    for násobek in range(x, x*y+1, x):
        if  násobek % y == 0:
            return násobek
print(nsn(2,3),nsn(24,3078), nsn(48, 42))
In [13]:
def nsd(x,y):
    m = min(x, y)
    for i in range(m, 1,-1):
        if je_dělitelné(x,i) and je_dělitelné(y,i):
            return i
    return 1   

def nsn(x,y):
    d  = nsd(x,y)
    return x * y // nsd(x,y)

print(nsn(2,3),nsn(24,3078), nsn(48, 42))
6 12312 336

Odhady, aproximace¶

  1. Napište funkci eulerovo_cislo(n), která spočítá a vrátí odhad Eulerova čísla $e$ pomocí $n$-tého členu posloupnosti $(1 + \frac{1}{n})^n$. Pomocí srovnání s hodnotou math.exp(1) zhodnoťte, na kolik desetinných míst je výsledek přesný.
In [7]:
import math
def eulerovo_cislo(n):
    return (1 + 1 / n) ** n

odhad = eulerovo_cislo(100000)
spravne = math.exp(1)
chyba = abs(odhad - spravne)
print(odhad, spravne, chyba)
2.7182682371922975 2.718281828459045 1.359126674760347e-05
  1. Číslo $\sqrt{2}$ lze zapsat pomocí nekonečného řetězového zlomku:

    $$ \sqrt{2} = 1 + \cfrac{1}{2 + \cfrac{1}{2 + \cfrac{1}{2 + \ddots}}} $$

    Napište funkci odmocnina_2(n), která spočítá odhad čísla $\sqrt{2}$ pomocí řetězového zlomku s $n$ jmenovateli. Zhodnoťte, na kolik desetinných míst je výsledek přesný.

In [21]:
a_1 = 1 + 1/2
a_2 = 1 + 1/ (1+ a_1)

import math
def odmocnina_2(n):
   vysledek = 1 + 1 / 2
   for k in range(1,n):
       vysledek = 1 + 1 / (1 + vysledek)
   return vysledek

odhad = odmocnina_2(10)
spravne = math.sqrt(2)
chyba = abs(odhad - spravne)
print(odhad, spravne, chyba)
1.4142135516460548 1.4142135623730951 1.0727040367086715e-08
  1. Napište funkci zlatý_řez(n), která spočítá odhad zlatého řezu $\varphi$ pomocí řetězového zlomku s $n$ jmenovateli. Také zhodnoťte, na kolik desetinných míst je výsledek přesný.

    Zlatý řez:

    $$ \varphi = \cfrac{1 + \sqrt{5}}{2} = 1 + \cfrac{1}{1 + \cfrac{1}{1 + \cfrac{1}{1 + \ddots}}} $$

In [ ]:
a_1 = 1 + 1 / 1 = 2
a_2 = 1 + 1 / a_1
In [24]:
def zlatý_řez(n):
    vysledek = 2
    for i in range(1,n):
        vysledek = 1 + 1 / vysledek
    return vysledek

odhad = zlatý_řez(10)
spravne = (1 + 5 ** 0.5) / 2
chyba = abs(odhad - spravne)
print(odhad, spravne, chyba)
1.6179775280898876 1.618033988749895 5.6460660007306984e-05
  1. Napište funkci ludolfovo_číslo(n), která spočítá a vrátí odhad Ludolfova čísla) $\pi$ pomocí součtu prvních $n$ členů Leibnizovy řady. Zhodnoťte, na kolik desetinných míst je výsledek přesný.

$$ \pi = 4\sum^\infty_{k=0} \frac{(-1)^k}{2k+1} = \frac{4}{1}-\frac{4}{3}+\frac{4}{5}-\frac{4}{7}+\frac{4}{9}-\cdots $$

In [10]:
import math
def ludolfovo_cislo(n):
    vysledek = 0
    for k in range(n+1):
        vysledek += (-1) ** k / (2*k + 1)
    return 4 * vysledek

odhad = ludolfovo_cislo(100000)
spravne = math.pi
chyba = abs(odhad - spravne)
print(odhad, spravne, chyba)
3.1416026534897203 3.141592653589793 9.999899927226608e-06