3. Funkce a moduly
Funkce
Více informací zde.
1
2
3
# doteď jsme se setkali s několika funkcemi, například
len([1,2,3])
max(1,5)
Dnes nás budou zajímat převážně ty uživatelsky definované.
1
2
3
4
5
6
7
8
9
# definice funkce
def subtract(a, b):
# vrácení hodnoty z funkce
return a - b
a = 10
subtract(2, 3)
subtract(3, 2)
Předpis pro definování funkce.
1
2
def <function_name>([<parameters>]):
<statement(s)>
1
2
3
4
5
6
7
8
9
10
11
# PEP8 - názvy funkcí podobně jako proměnné, rovněž dbáme na vhodné pojmenování parametrů
# správně
def multiply_point(point, by):
x, y = point
return x * by, y * by
# špatně
def multiplyPoint(a, b):
x, y = a
return x * b, y * b
Předávání argumentů (vstup funkce).
1
2
3
4
5
6
7
8
9
10
subtract(a=2, b=3)
subtract(b=3, a=2)
subtract(3)
subtract(3, b=2)
# chyba, nejprve musí být povinné parametry
subtract(b=2, 3)
# již správně
subtract(3, b=2)
Výchozí parametr.
1
2
3
4
5
6
7
def subtract(a, b=1):
return a - b
subtract(3)
subtract(3, 2)
subtract(3, b=2)
Pozor na mutovatelné výchozí parametry.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def my_list(list_=[]):
list_.append("123")
return list_
# problém
my_list([1, 2, 3])
my_list([1, 2, 3])
my_list()
my_list()
# jak poznáme, že se jedná o stejný objekt?
my_list() is my_list()
# řešení problému
def my_list(list_=None):
if list_ is None:
list_ = []
list_.append("123")
return list_
# vše se chová jak očekáváme
my_list([1, 2, 3])
my_list([1, 2, 3])
my_list()
my_list()
my_list() is my_list()
Předání argumentu je v Pythonu řešeno hybridním způsobem mezi “předání hodnotou” a “předání odkazem”. Funkci je předán odkaz na objekt, odkaz je ale předáván hodnotou.
1
2
3
4
5
6
7
8
name = "Lukáš Novák"
def f(name):
name = "Petr Novák"
f(name)
print(name)
To však neznamená, že se obsah argumentu nikdy nemůže měnit v lokální funkci, stačí použít mutovatelné struktury.
1
2
3
4
5
6
7
8
names = ["Lukáš Novák", "Karel Novák"]
def change_name(names):
names[0] = "Petr Novák"
change_name(names)
print(names)
Příkaz return
podrobněji
Příkaz return
okamžitě ukončí veškeré vykonávání funkce (včetně cyklů) a vrátí uvedenou hodnotu.
1
2
3
4
5
6
7
8
9
10
def member(target, iterable):
for idx, item in enumerate(iterable):
if item == target:
return idx
return False
member(4, [1, 2, 3, 4, 5, 6, 7])
member(10, [1, 2, 3, 4, 5, 6, 7])
Pokud return
není uveden, vrací se automaticky hodnota None
.
1
2
3
4
5
6
7
8
9
def nothing():
pass
nothing() is None
def nothing():
return
nothing() is None
Příkaz return
můžeme v kombinaci se sekvencí tuple
použít na vrácení několika hodnot.
1
2
3
4
5
6
7
8
9
10
def multiply_point(point, by):
x, y = point
# konstruktor tuple
return x * by, y * by
multiply_point((10, 5), 2)
# tuple unpacking
new_x, new_y = multiply_point((10, 5), 2)
Tuple unpacking je užitečný pro předání seznamu/tuple argumentů.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def multiply_point(x, y, by):
return x * by, y * by
point = (10, 5)
# chyba
multiply_point(point, 2)
# správně, dojde k unpackingu pomocí operátoru *
multiply_point(*point, 2)
# příklad z praxe, transformace matice
zip(*[[1, 2, 3], [4, 5, 6]])
# slovník lze použít pro pojmenované argumenty
point = {"x": 10, "y": 5}
# unpacking pomoci operatoru **
multiply_point(**point, by=2)
Volitelný počet argumentů
Volitelný počet argumentu pomocí tuple.
1
2
3
4
5
def average(*args):
return sum(args) / len(args)
average(1, 2, 3, 4)
Volitelný počet argumentů pomocí slovníku.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def print_name(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_name(title="Ing.", firstname="Lukáš", lastname="Novák")
print_name("Novák", title="Ing.", firstname="Lukáš")
def print_name(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
person = {"title": "Ing.", "firname": "Lukáš", "lastname": "Novák", "job": "programmer"}
print_name(**person)
Kombinace předchozích možností.
1
2
3
4
5
6
7
def func(a, b, *args, **kwargs):
print(a)
print(b)
print(args)
print(kwargs)
func(1, 2, "ahoj", "svete", x=11, y=22)
Docstrings
Každá definice funkce může (a měla by) obsahovat takzvaný docstring. Jedná se o řetězec s krátkou dokumentací funkce v anglickém jazyce.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def subtract(a, b):
"""Subtract b from a."""
return a - b # V případě jednořádkového docstringu zde nevkládáme prázdný řádek.
def subtract(a, b):
"""Subtract b from a.
Args:
a: Number to subtract from.
b: Number being subtracted.
Returns:
Result of subtraction.
"""
return a - b # V případě víceřádkového docstringu zde vkládáme prázdný řádek.
subtract.__doc__
help(subtract)
Od této chvíle bude vyžadována dokumentace ve formě docstringů u všech funkcí, které se v domácích úkolech vyskytnou.
Příkaz assert
Příkaz assert
je vhodným nástrojem defenzivního programování, umožňuje nám za běhu programu zaručit, že jsou uvedené výrazy platné.
1
2
3
4
5
6
7
8
a = 10
assert a == 10
def subtract(a, b):
"""Subtract b from a."""
return a - b
assert subtract(10, 5) == 5
Je to jednoduchý nástroj jak ověřovat základní funkčnost funkcí (k pokročilejšímu testování později). Je však nutné zmínit, že assert
do finálního kódu nepatří.
Moduly a balíčky
Doteď jsme náš kód organizovali do jednotlivých skriptů (samostatně fungující soubor). S příchodem funkcí je vhodné dělit definice funkcí na různé soubory a z těchto souborů potom uživatelsky definované funkce importovat do dalších skriptů. Tímto je možné sdílet funkcionalitu mezi více projektů a vytvářet knihovny.
Modul je tedy soubor obsahující Python definice a příkazy. Název modulu je název souboru s příponou .py
. V rámci modulu můžeme název modulu získat pomoci speciální proměné __name__
.
Ukázka importování vestavěného modulu math
pomoci příkazu import
.
1
2
3
4
5
6
import math
math.sqrt(6)
math.__name__
Vlastní modul pak můžeme vytvořit jednoduše. Vytvořme soubor list_operations.py
s následujícím obsahem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def subtract_lists(list1, list2):
"""Subtract two list piecewise."""
result = []
for a, b in zip(list1, list2):
result.append(a - b)
return result
def sum_lists(list1, list2):
"""Sum two list piecewise."""
result = []
for a, b in zip(list1, list2):
result.append(a + b)
return result
V jiném skriptu umístěném v téže složce (nebo v interpretu spuštěném z téže složky - vhodné pro rychlé testování) můžeme modul list_operations
importovat.
1
2
3
4
import list_operations
list_operations.subtract_lists([1, 2], [4, 3])
Modul nemusí obsahovat pouze definice ale i příkazy, tyto příkazy jsou zamýšleny jako inicializace modulu a jsou provedeny pouze jednou při načtení modulu.
Každý modul má pak odpovídající jmenný rozsah, proto můžeme používat dvě funkce z rozdílných modulů se stejným názvem.
1
2
3
4
5
6
import math
import my_math
math.sqrt
my_math.sqrt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# PEP8 - jednotlivé importy musí být na samostatném řádku
# správně
import os
import sys
# špatně
import sys, os
# PEP8 - pořadí importů
import sys # systémová knihovna
import numpy # externí knihovna
import my_math # lokální modul
Existuje varianta import
příkazu, který přímo importuje jména z jmenného prostoru modulu do aktuálního jmenného prostoru.
1
2
3
4
from list_operations import subtract_lists, sum_lists
subtract_lists([1, 2], [4, 3])
1
2
3
# PEP8 - jednotlivé importy musí být na samostatném řádku
# toto je výjimka z pravidla
from list_operations import subtract_lists, sum_lists
Variantu, která importuje kompletní jmenný prostor modulu do toho aktuálního, je lepší nepoužívat.
1
from list_operations import *
Přejmenování modulu při importu je však užitečné.
1
2
3
4
import list_operations as loperations
loperations.subtract_lists([1, 2], [4, 3])
Rovněž je možné přejmenovat importované funkce.
1
from list_operations import subtract_lists as lsubtract
Pokud do modulu umístíme následující podmínku, obsah bude vykonán pouze při přímem spuštění zdrojového kódu, nikoli při jeho importování.
1
2
if __name__ == "__main__":
print("Only when script is launched.")
Odkud se moduly importují?
Při importu modulu my_math
hledá interpret nejdříve modulu vestavěné. Pokud nebyl žádný vestavěný modul nalezen, hledá soubor s názvem my_math.py
v seznamu adresářích uložném v proměnné sys.path
. Seznam obsahuje adresář odkud byl skript spuštěn, PYTHONPATH
s cestami k instalovaným modulů a další.
Jak a kde používat odřádkování
Při psaní kódu je žádoucí kód strukturovat pro jeho lepší čitelnost. Bloky kódu oddělujeme na vhodných místech jedním nebo vícero prázdnými řádky.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys # systémová knihovna, prázdný řádek
import numpy
import pandas # externí knihovna, prázdný řádek
import my_math
from . import calculator # lokální modul, prázdný řádek
# prazdný řádek nad prvním řádkem pod importy
def subtract_lists(list1, list2):
"""Subtract two list piecewise.""" # pod docstring neumisťujeme žádný řádek
result = [] # zde budeme agregovat vysledek, prazdny řádek
for a, b in zip(list1, list2):
result.append(a - b) # ukončení bloku s cyklem, prázdný řádek
return result
# dva prazdné řádky mezi funkcemi
def sum_lists(list1, list2):
"""Sum two list piecewise."""
result = []
for a, b in zip(list1, list2):
result.append(a + b)
return result
Balíčky
Balíčky jsou způsob jak strukturovat jmenný prostor Python modulu. Jmenný prostor je potom přístupný skrze tečkovou notaci.
1
2
# z modulu A importujeme submodul B
import A.B
Příklad struktury balíčku:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
Balíček sound
má podřazené balíčky formats
, effects
a další.
Jakmile balíček importujeme, Python automaticky dohledá veškeré podsložky balíčků.
Speciální soubor __init__.py
je potřebný k tomu, aby Python považoval složku za balíček. Pro základní funkcionalitu stačí, aby se jednalo o prázdný soubor, může zde však být provedena inicializace balíčku.
Z takové struktury může uživatel importovat následujícím způsobem:
1
2
3
4
5
import sound.effects.echo
# aby mohl být modul použít, musí být napsáno jeho plné jméno
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
Alternativně však můžeme použít následující:
1
2
3
4
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)
Posledním způsobem je potom importování samostatné funkce echofilter
.
1
2
3
4
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)
Reference v rámci balíčku
Často je potřeba provádět relativní import v rámci balíčku. Relativní import je možné provést následovně:
1
2
3
4
5
# v rámci složky
from . import echo
# nadřazená složka
from .. import formats
from ..filters import equalizer
Instalace externích balíčku
V budoucnu rozebereme detailněji, aktuálně pro nás bude relevantní, že balíčky v drtivé většině instalujeme z Python Package Index (PyPI) pomocí nástroje pip
.
Detailnější popis nalezneme v dokumentaci.
Od dnešního semináře budou testy na splnění úkolu realizované externí knihovnou pytest
, kterou je tedy možné instalovat příkazem:
1
pip install pytest
Posléze ve složce s repozitářem úkolu L03E01 stačí spustit příkaz (kde soubor tests.py
obsahuje testy):
1
pytest tests.py
A v případě úkolu obsahující balíček (například L03E02) je nutné balíček nejprve lokálně nainstalovat a poté testy spustit:
1
2
pip install -e .
pytest
Zatím není nutné předchozím krokům rozumět, o publikování a instalovaní balíčků si více řekneme na konci tohoto kurzu.
Úkoly
Nevíte si rady? Přečtěte si “Jak pracovat s Github Classroom?”.
Při řešení úloh nepoužívejte pokročilejší funkcionalitu jazyka která nebyla ještě představena! Takové úkoly budou vráceny na přepracování bez ohledu na jejich funkčnost.
Skupina Mikula
- L03E01: Read points [Náhled], [Příjmout úkol]
- L03E02: Algebra package [Náhled], [Příjmout úkol]
Skupina Petržela
- L03E01: Read points [Náhled], [Příjmout úkol]
- L03E02: Algebra package [Náhled], [Příjmout úkol]