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

Skupina Petržela