KMI/ZPP2 - 10. seminář - Organizace zdrojového kódu

Dosud jsme jako základní nástroj pro abstrakci funkce, které nám sloužili k rozdělení kódu do jednotlivých částí vykonávající nějaký výpočet. Pokud bychom ale měli k dispozici jen funkce, tak by při větších projektech měl soubor se zdrojovým kódem několik desítek tisíc řádků. V takovém případě by orientace v takovém kódu byla velmi náročná.

Proto nám jazyk Python (a další jazyky) umožňuje rozdělit zdrojový kód do samostatných souborů, kde v jednom souboru máme funkce sloužící například pro práci se seznamy. Tímto docílíme ještě lepšího rozdělení zdrojového kódu. Soubor s funkcemi které slouží k řešení konkrétního problému nazýváme knihovna.

Moduly

Jako modul označujeme v jazyce Python soubor s příponou .py. Jedná se o běžný soubor se zdrojovým kódem, u kterého předpokládáme, že bude vložen do jiného souboru. Nepředpokládá se tedy spuštění takového souboru samostatně. Jako příklad si vytvoříme soubor rmath.py který bude obsahovat následující funkci:


    PRECISION = 0.00000001
    # výpočet druhé odmocniny pomocí Newtonovy metody
    # https://cs.wikipedia.org/wiki/Metoda_tečen
    def my_sqrt(number):
        estimate = number
        while True:
            new_estimate = 0.5 * (estimate + number / estimate)
            if abs(new_estimate - estimate) < PRECISION:
                return new_estimate
            estimate = new_estimate
            
    # výpočet n-té mocniny
    def my_power(base, exponent):
        return float(base ** exponent)

Vložení modulu

Pro vložení knihovny používáme příkaz import.


    import file

Po vykonání příkazu import se provede program který, je zapsaný v souboru a vytvoří se nová proměnná pomocí které přistupujeme k funkcím a proměnným zapsaných v importovaném souboru.


    # importování souboru rmath.py
    import rmath
    
    # zavolání funkce my_sqrt
    rmath.my_sqrt(2)
    # výpis konstanty PRECISION
    print(rmath.PRECISION)

Jméno pomocí kterého přistupujeme k funkcím a proměnným se nazývá namespace (jmenný prostor). Díky tomu víme z jaké knihovny danou funkci (proměnnou) používáme. Příkaz import umožňuje přejmenovat namespace.


    import file as name

Příklad


    import rmath as m
    
    m.my_sqrt(2)

V jazyce Python je možné importovat pouze část importovaného souboru, například pouze jednu funkci nebo proměnnou.


    from file import part

Například můžeme chtít importovat pouze funkci my_power


    from rmath import my_power
    
    my_power(2, 10)

Importovanou část programu je možné přejmenovat (pomocí as) a je možné importovat více částí současně.


    # import a přejmenování více částí programu
    from rmath import my_sqrt as f1, my_power as f2
    
    f1(2)    # volání funkce my_sqrt()
    f2(2, 2) # volání funkce my_power()

V případě, že chceme importovat celý obsah souboru, lze použít symbol * (hvězdička).


    from rmath import *

Pro úplnost dodejme, že při použití from příkazu dochází k importování pouze specifikovaných částí.


    from rmath import my_sqrt
    
    # funkce funguje správně (má přístup ke konstantě PRECISION)
    my_sqrt(2) # vypíše: 1.414213562373095
    
    # náš program nemá přístup ke konstantě PRECISION
    print(PRECISION) # způsobí chybu: NameError: name ’PRECISION’ is not defined

Pro lepší organizaci zdrojového kódu můžeme soubory umisťovat do adresářů. Při importu, pak vkládáme celou relativní cestu k souboru se zdrojovým kódem. Jako oddělovače adresářů používáme symbol . (tečka).


    import my_library.rmath
    
    # použití
    my_library.rmath.my_sqrt(2)
    
    # NEBO
    
    import my_library.rmath as m
    
    # použití
    m.my_sqrt(2)

Existující knihovny

Jazyk Python obsahuje spoustu předprogramovaných knihoven. Příkladem může být knihovna math.


    # import knihovny math (součást jazyka Python)
    import math
    
    # druhá odmocnina
    print(math.sqrt(2))

Všechny dostupné knihovny lze najít na adrese: https://docs.python.org/3/library/.

Úkoly

Vytvořte modul matice_txt_IO.py, který zapíše matici do textového souboru a matici z takto vytvořeného souboru opět přečte. Pro práci můžete využít úkoly z minulého semináře. Příklad použití:
    M = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    matice_txt_IO.uloz_matici("matice.txt", M)
    nactena_matice = matice_txt_IO.nacti_matici("matice.txt")
    print(nactena_matice)
    
Vypíše
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
Obsah souboru matice.txt
    1,2,3 
    4,5,6
    7,8,9
    
Vytvořte modul tree.py, který implementuje uspořádaný binární strom. Modul bude poskytovat tyto funkce:
    tree_add(tree_root, hodnota)
    tree_find(tree_root, hodnota)
    
Modul importujte do programu main_tree.py, kde ověříte funkčnost. Obsah main_tree.py bude mj. obsahovat:
    tree_root = []
    tree_add(tree_root, 42)
    tree_add(tree_root, 15)
    tree_add(tree_root, 16)
    tree_add(tree_root, 72)
    tree_add(tree_root, 5)
    
    print(tree_find(tree_root, 16))
    print(tree_find(tree_root, 5))
    print(tree_find(tree_root, 88))
    
Po spuštění program vypíše:
    True
    True
    False
    
Pro implementaci můžete využít kódu z 5. semináře.
Vytvořte modul fronta.py, ve kterém implementuje tyto funkce:
    vytvor_frontu()
    pridej_do_fronty(fronta, hodnota)
    odeber_z_fronty(fronta)
    
Modul importujte do programu main_fronta.py, kde ověříte funkčnost. Obsah main_fronta.py bude následující:
    import fronta

    f1 = fronta.vytvor_frontu()
    fronta.pridej_do_fronty(f1, "Hello")
    fronta.pridej_do_fronty(f1, "world")
    f2 = fronta.vytvor_frontu()
    fronta.pridej_do_fronty(f2, "fist")
    fronta.pridej_do_fronty(f2, "second")

    print(fronta.odeber_z_fronty(f1))
    print(fronta.odeber_z_fronty(f1))
    print(fronta.odeber_z_fronty(f2))
    print(fronta.odeber_z_fronty(f2))

    print(fronta.odeber_z_fronty(f1))
    print(fronta.odeber_z_fronty(f2))
    
Po spuštění program vypíše:
    Hello
    world    
    fist
    second
    None
    None
    
Bude možnost vytvářet libovolný počet front. Do front bude možno prvky libovolně přidávat a získávat. Při pokusu o odebrání z prázdné fronty bude vráceno None. Nápověda: Pro uchování odkazu na frontu je možno samotnou frontu "obalit" seznamem.