2. Funkce a moduly
Obsah
Funkce
Více informací zde.
# 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é.
# 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.
# definice funkce
def <function_name>([<parameters>]):
<statement(s)>
Dbejte na základní formátování kódu!
# 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
# PEP8 - před a po definici funkce patří dva prázdné řádky
# správně
import math
def add(a, b):
return a + b
def sub(a, b):
return a - b
number = sub(add(5, 10), 15)
# špatně
import math
def add(a, b):
return a + b
def sub(a, b):
return a - b
number = sub(add(5, 10), 15)
Předávání argumentů (vstup funkce).
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.
def subtract(a, b=1):
return a - b
subtract(3)
subtract(3, 2)
subtract(3, b=2)
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.
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.
def change_name(names):
names[0] = "Petr Novák"
names = ["Lukáš Novák", "Karel 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.
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.
def nothing():
pass
def nothing2():
return
nothing() is None
nothing2() is None
Příkaz return můžeme v kombinaci se sekvencí tuple použít na vrácení několika hodnot.
def multiply_point(point, by):
x, y, z = point
# konstruktor tuple
return x * by, y * by, z * by
multiply_point((10, 5, 1), 2)
# tuple unpacking
new_x, new_y, new_z = multiply_point((10, 5, 1), 2)
# pokud nějakou hodnotu nepotřebujeme, namísto jména proměnné použijeme _
new_x, _, _ = multiply_point((10, 5, 1), 2)
# unpacking zbytku hodnot do seznamu, tj. new_x = 20, rest = [10, 2]
new_x, *rest = multiply_point((10, 5, 1), 2)
Tuple unpacking je užitečný pro předání seznamu/tuple argumentů.
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.
def average(*args):
return sum(args) / len(args)
average(1, 2, 3, 4)
Volitelný počet argumentů pomocí slovníku.
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í.
def func(a, b, *args, **kwargs):
print(a)
print(b)
print(args)
print(kwargs)
func(1, 2, "ahoj", "svete", x=11, y=22)
lambda výraz
Slouží k vytváření krátkých anonymních funkcí (funkce bez identifikátoru - názvu). Tělo funkce je syntakticky omezeno na jeden výraz a mohou být použity všude, kde je vyžadována funkce. Sémanticky se jedná o syntaktický cukr pro klasickou funkci.
def apply_operation(operation, a, b):
return operation(a, b)
def my_sum(a, b):
return a + b
assert apply_operation(my_sum, 2, 3) == 5
assert apply_operation(lambda a, b: a + b, 2, 3) == 5
# anonymní funkce pro součet dvou čísel
lambda a, b: a + b
# Out[1]: <function __main__.<lambda>(a, b)>
(lambda a, b: a + b)(1, 2)
# Out[2]: 3
Hezkým příkladem je použití v parametru key u funkce sorted.
# seznam obsahující čísla
sorted([4, 2, 3, 1, 4])
# seznam obsahující n-tice
sorted([(1, "Lukáš Novák", 10),
(4, "Albert Billa", 1),
(0, "Pepa Los", 30)])
# Out[0]: [(0, 'Pepa Los', 30), (1, 'Lukáš Novák', 10), (4, 'Albert Billa', 1)]
# seřazeno dle posledního prvku
sorted([(1, "Lukáš Novák", 10),
(4, "Albert Billa", 1),
(0, "Pepa Los", 30)], key=lambda x: x[2])
# Out[1]: [(4, 'Albert Billa', 1), (1, 'Lukáš Novák', 10), (0, 'Pepa Los', 30)]
Globální proměnné
Práce s globální proměnnou z funkce probíhá pomocí příkazu global, který specifikuje jaké proměnné jsou v dané funkci hledány v globálním prostředí.
global_var = 10
# funkce jen lokálně vytvoří novou proměnnou global_var
def test():
global_var = 20
# vypíše se 10
test()
print(global_var)
# spráné nastavení globální proměnné pomocí global
def test():
global global_var
global_var = 42
# vypíše se 42
test()
print(global_var)
Docstring
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.
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é.
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
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.
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:
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.
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.
import math
import my_math
math.sqrt
my_math.sqrt
# 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é knihovny
import os
import numpy # externí knihovny
import pandas
import my_math # lokální moduly
import my_data
import my_os
# samotný kód následuje za minimálně jedním prázdným řádkem (dvěma, v případě definice funkce nebo třídy)
...
Existuje varianta import příkazu, který přímo importuje jména z jmenného prostoru modulu do aktuálního jmenného prostoru.
from list_operations import subtract_lists, sum_lists
subtract_lists([1, 2], [4, 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.
from list_operations import *
Přejmenování modulu při importu je však užitečné.
import list_operations as loperations
loperations.subtract_lists([1, 2], [4, 3])
Rovněž je možné přejmenovat importované funkce.
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í.
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.
import sys # systémová knihovna, prázdný řádek
import numpy
import pandas # externí knihovny, prázdný řádek
import my_math
from . import calculator # lokální moduly, prázdný řádek
def subtract_lists(list1, list2): # dva prazdné řádky nad definici funkce
"""Subtract two list piecewise.""" # pod jednořádkový docstring neumisťujeme žádný prázdný řádek
result = [] # zde budeme agregovat vysledek, prazdny řádek před blokem cyklu
for a, b in zip(list1, list2):
result.append(a - b) # ukončení bloku s cyklem, prázdný řádek
return result
def sum_lists(list1, list2): # dva prazdné řádky mezi funkcemi
"""Sum two list piecewise.
Args:
list1: List
list2: List
Returns:
List of added numbers from first and second list.
""" # pod víceřádkový docstring umisťujeme jeden prázdný řádek
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.
# z modulu A importujeme submodul B
import A.B
Příklad struktury balíčku:
# z modulu A importujeme submodul B
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 slouží k inicializaci balíčku. V Pythonu 2 byl soubor nezbytný, proto aby složka byla považována za balíček, od Pythonu 3 je to nepovinné (ale stále běžně používané pro jasnost a kompatibilitu). Pro základní funkcionalitu stačí, aby se jednalo o prázdný soubor, ale můžeme v něm třeba definovat proměnné, funkce, nebo importovat podmoduly, aby se lépe používaly. Kód uvnitř __init__.py se spustí při prvním importu balíčku.
Z takové struktury může uživatel importovat následujícím způsobem:
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í:
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.
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ě:
# 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:
pip install pytest
Posléze ve složce s repozitářem úkolu H02 stačí spustit příkaz (kde soubor tests.py obsahuje testy):
pytest tests.py
A v případě úkolu obsahující balíček (například H02) je nutné aby i složka s testy obsahovala soubor __init__.py, jinak balíček musíme nejprve lokálně nainstalovat příkazem pip install -e .. Poté testy můžeme spustit:
pytest