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:
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
print(print_demo2)
<function print_demo2 at 0x739d20632840>
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
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:
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:
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:
demo_funkce_s_mnoha_parametry(1, 2)
param1 je 1 param2 je 2 param3 je 10 param4 je None
demo_funkce_s_mnoha_parametry(1, 2, 3)
param1 je 1 param2 je 2 param3 je 3 param4 je None
demo_funkce_s_mnoha_parametry(1, 2, 3, 4)
param1 je 1 param2 je 2 param3 je 3 param4 je 4
# ukázka: funkce round + Show Contextual Help
print(round(7.8123))
round(7.8123, 3)
8
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í:
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
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
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:
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.
# 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í:
- Vytvoří se objekt, který reprezentuje danou funkci a obsahuje kód definovaný v těle funkce.
- 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í:
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:
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):
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:
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¶
- 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).
def ano_ne(otazka):
return True
vysledek1 = ano_ne("Mas rad programovani?")
vysledek2 = ano_ne("Mas rad matematiku?")
print(vysledek1, vysledek2)
True True
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
- Upravte předchozí funkci do tvaru
ano_ne(otázka, počet_pokusů)
, kde číslopočet_pokusů
omezí maximální počet opakování cyklu.
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
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
- 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.
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é¶
- Napište funkci
prvociselny_rozklad(n)
, která vypíše prvočíselný rozklad přirozeného číslan
.
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
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
- Napište funkci
je_prvocislo(n)
, která vrátíTrue
, pokud přirozené číslon
je prvočíslo. Jinak vrátíFalse
.
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
- Napište funkci
vypis_prvocisla(n)
, která vypíše všechna prvočísla menší než přirozené číslon
.
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
- Napište funkci
kte_prvocislo(k)
, která spočítá a vrátík
-té prvočíslo.
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
- Napište funkci
nsn(a, b)
, která spočítá a vrátí nejmenší společný násobek přirozených čísela
ab
.
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))
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¶
- 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 hodnotoumath.exp(1)
zhodnoťte, na kolik desetinných míst je výsledek přesný.
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
Čí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ý.
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
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}}} $$
a_1 = 1 + 1 / 1 = 2
a_2 = 1 + 1 / a_1
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
- 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 $$
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