Loeng 10: Modulaarsus, lausend import, moodul, pakett, peaprotseduur, __name__ == '__main__', Pythoni standardteegid, kolmandate osapoolte teegid ja moodulid, Python Package Index (PyPI), Pythoni distributsiooni haldamine¶

Viimati uuendatud 2.11.2024.

Koodinäited¶

Modulaarsus ja koodistruktuur¶

Modulaarne programmeerimine¶

Suuremate programmide lähtekoodi pole otstarbekas salvestada ühte faili. Tarkvara paremaks haldamiseks ja koodide taaskasutuse võimaldamiseks on programmi lähtekood tihti jaotatud erinevatesse failidesse.

  • Paljude tänapäeva programmide taga on meeletu hulk koodi. Probleemi ja sellele vastav lahendus jagatakse osadeks.
    • Kui olemasolevad alaüksused on ikka liiga keerulised, siis jagatakse need omakorda veel osadeks jne, kuni saadakse paraja suurusega ülesanded, mida programmeerija suudab oma peas piisava täpsusega hallata.
  • Loodud alaüksused ja neile vastavad lahendused (st. programmiosad) peavad üksteisest võimalikult vähe sõltuma, vastasel juhul peab programmeerija mõtlema mitmele ülesandele korraga.
  • Funktsioonide/protseduuride (või muude objektide) lokaalne skoop võimaldab vajadusel funktsiooni/protseduuri sisu ümber kirjutada ilma, et see põhjustaks ootamatusi ülejäänud süsteemis.

Projekti koodi loetavus¶

Mõned inimesed arvavad, et kui kood töötab, siis töö sellega lõppeb. Tõenäoliselt need inimesed ei saa kunagi headeks progejateks. Kuna programmeerimine nõuab tihti rühmatööd, peab kood olema lisaks autorile ka teistele arusaadav. Isegi projekti üksi arendades võib olla kogu protsess drastiliselt raskendatud, kui sa ei jälgi koodi modulaarsuse ja struktureerituse reegleid.

Moodul¶

Pythoni moodul on .py laiendiga fail, mis sisaldab Pythoni programmeerimiskeeles kirjutatud lähekoodi (funktsioonide, protseduuride, muutujate/atribuutide ja klasside definitsioone, jne).

  • Pythoni mooduli nimi on faili nimi ilma selle laiendita, nt. foo.py korral on mooduli nimi foo
  • .pyc, .pyo $-$ Pythoni ettekompileeritud ja optimiseeritud koodid .py failist
  • .so (Linux), .dll (Windows) $-$ Pythoni laiendusmoodulid (kompileeritud teegid)

Tänases loengus: Alustame enda loodud moodulitega ja siis proovime importida Pythoni standardteeke ning lõpuks ka kolmandate osapoolte teeke ja mooduleid.


Enda loodud moodulite import¶

Paiguta programmi mooduli failid samasse kausta $-$ projekti töökausta.

Moodul moodulFunkid (moodulFunkid.py) sisaldab kursuse jooksul loodud funktsioonide definitsioone. Joonis 1 kujutab kahte moodulit mis asuvad samas arvuti failisüsteemi kaustas ehk kataloogis. Kursuse kodulehelt on võimalik alla laadida Joonisel 1 kujutatud projekti failid mis on salvestatud faili 1.zip.

No description has been provided for this image
Joonis 1. Moodulid mis asuvad samas kataloogis ehk kaustas.

NB! Seega, kui soovid teatud moodulis defineeritud objekte taaskasutada mingis muus moodulis paiguta mõlemad moodulid samasse töökausta.

Lausend import¶

Teise mooduli kasutamiseks, hetkel avatud moodulis, tuleb see importida kasutades lausendit import. Importida saame terve mooduli (import või konstruktsioon import/as või konstruktsioon from/import/*) või konktreetseid objekte kasutades konstruktsioone from/import, from/import/as. Kõik erinevad inportimise viisid on loetletud allpool:

import <moodul>

import <moodul> as <nimi>  # Tavaliselt lühendame nime.

from <moodul> import <atribuut1>, <atribuut2>, <funktsioon1>, <klass1>

from <moodul> import <atribuut1> as <nimi>

from <moodul> import *  # Impordib kõik, koodis mooduli nimele viitamine pole vajalik.

Kui imporditavat moodulit ei leita, siis tõstatakse erisus ImportError.

Näide: Avame ja uurime moodulit Loeng10_1 millega on kaasas moodul moodulFunkid (1.zip fail).

In [1]:
import moodulFunkid

print(dir(moodulFunkid))  # Mooduli sisu.
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'astenda', 'cacheit', 'factorial', 'fibonacci', 'keskmine', 'kuup', 'loendi_keskmine', 'my_map', 'namefunc', 'pi', 'polunoom', 'recursive_factorial', 'recursive_fibonacci', 'ruut', 'ruutvorrand', 'ruutvorrand1', 'ruutvorrand2', 'solkija']

Kindlale objektile viitamiseks kasutame punkti <moodul>.<objekt>:

In [2]:
print(moodulFunkid)  # Mis andmetüübiga on tegu?

print(moodulFunkid.ruutvorrand)  # Viitan kindlale objektile.
<module 'moodulFunkid' from '/Users/dmitrikartofelev/Documents/WORK Teaching/YFX0500/Loeng 10/moodulFunkid.py'>
<function ruutvorrand at 0x1043f3060>

Moodulis defineeritud atribuudile ehk muutujanimele viitamine:

In [3]:
moodulFunkid.pi  # Atribuut, moodulis asuv muutuja.
Out[3]:
3.141592653589793

Moodulis defineeritud funktsiooni kasutamine:

In [4]:
x1, x2 = moodulFunkid.ruutvorrand(1, 2, 3)
#            ^            ^       ^  ^  ^
#         moodul      funktsioon  argument

print('Ruutvorrandi lahend x1 =', x1)
print('Ruutvorrandi lahend x2 =', x2)
Ruutvorrandi lahend x1 = (-0.9999999999999999+1.4142135623730951j)
Ruutvorrandi lahend x2 = (-1-1.4142135623730951j)

Keegi ei keela meil imporditud funktsioonile uut nime andmast:

In [5]:
rv = moodulFunkid.ruutvorrand  # Uus nimi.

print('Nimetasin ümber', rv(-3, 5, 7))
Nimetasin ümber (-0.9067177514850918, 2.5733844181517584)

Tihti on kasulik mooduli nime lühendada. Impordime mooduli moodulFunkid uuesti:

In [6]:
import moodulFunkid as mF  # Lühendan mooduli nime.

x1, x2 = mF.ruutvorrand(1, 2, 3)  # Väljakutse uue nimega.

print('Ruutvorrandi lahend x2 =', x2)
Ruutvorrandi lahend x2 = (-1-1.4142135623730951j)
In [7]:
from moodulFunkid import ruutvorrand  # Kindla funktsiooni import.

x1, x2 = ruutvorrand(-1, -2, 33)  # Nüüd pole vaja viidata moodulile.

print('Ruutvorrandi lahend x2 =', x2)
Ruutvorrandi lahend x2 = 4.830951894845301
In [8]:
from moodulFunkid import ruutvorrand as rv  # Lühendame kindla funktsiooni nime.

x1, x2 = rv(-11, -2, 3)  # pole vaja viidata moodulile

print('Ruutvorrandi lahend x2 =', x2)
Ruutvorrandi lahend x2 = 0.4391774449859364

Kõikide objektide import:

In [9]:
from moodulFunkid import *  # Impordib nimed kohalikku nimeruumi.

x1, x2 = ruutvorrand(-3, 2, 3)  # Pole vaja viidata mooduli nimele.
print('Ruutvorrandi lahend x2 =', x2)

print('Pi =', pi)  # Pole vaja viidata mooduli nimele.
Ruutvorrandi lahend x2 = 1.3874258867227933
Pi = 3.141592653589793

Pakett¶

Pythoni pakett on failisüsteemi kataloogi puu, mille iga kataloog (kaust) sisaldab vähemalt ühte Pythoni moodulit nimega __init__ (__init__.py). Fail __init__.py on tühi või sisaldab suhtelisi viiteid.

  • Pythoni paketi nimi on kataloogi (kausta) nimi.
  • Pythoni paketi kasutus sarnaneb moodulil kasutusele.

Joonis 2 kujutab Pythoni paketi struktuuri ja faile.

No description has been provided for this image
Joonis 2. Pythoni paketti struktuur koos __init__.py failiga.

Tühja faili __init__.py kasutamine¶

Kui __init__.py fail on tühi siis impordime paketti kasutades järgmist süntaksit. Nii nagu eespool moodulite puhul toetatakse ka siin viite erinevat importimise viisi:

import <pakett>.<moodul>

import <pakett>.<moodul> as <nimi>

from <pakett>.<moodul> import <atribuut1>, <atribuut2>, <funktsioon1>, <klass1>

from <pakett>.<moodul> import <funktsioon1> as <nimi>

from <pakett>.<moodul> import *  # Impordib kõik, mooduli nimele viitamine pole vajalik.

Alampaketi mooduli import:

import <pakett>.<alampakett>.<moodul>

Mittetühja faili __init__.py kasutamine¶

Kui fail __init__.py sisaldab suhtelisi viiteid kõikidele objektidele mida projektis on plaanitud kasudada. Viidete kasutamine lühendab importimise süntaksit. Tüüpiline __init__.py faili sisu:

from .<moodul1> import <funktsioon1>
from .<moodul2> import *

Punktide kasutus: . tähistab paketti, .. alampaketti ja ... alamalampaketti jne. Kui moodulis __init__ (__init__.py) on eelmainitud suhtelised viited toimub import järgmiselt:

import <pakett>  # Juurdepääs kõikide moodulite kõikidele objektidele.

import <pakett> as <nimi>

from <pakett> import <funktsioon1 (moodul1)>, <atribuut1 (moodul2)>  # Näiteks.

from <pakett> import <funktsioon2> as <nimi>

from <pakett> import *

Vajadus viidata kindlale kaustas asuvale moodulile pole enam vajalik. Relatiivsed viited lubavad pakettil käituda sarnaselt moodulile. Imporditud moodulite ja pakketide sisu järgivaatamiseks kasutame endiselt funktsiooni dir.

Paketi kasutamise näited¶

Näited praktikumis või kodus uurimiseks: Avame ja uurime mooduleid Loeng10_2 ja Loeng10_3 ning nendega kaasaskäivat paketti nimega pakettFunkid:

  • Failis 2.zip on salvestatud Pythoni projekt mis sisaldab paketti koos tühja __init__.py failiga
  • Failis 3.zip on salvestatud Pythoni projekt mis sisaldab paketti koos mittetühja viidetega __init__.py faili.

Praktikumi 10 ülesanded sisaldavad lisajuhiseid mis aitavad edukalt eelmainitud paketti kasutada.

Spetsiaalne muutuja __name__¶

Peaprotseduur ja konstruktsiooni if __name__ == '__main__': kasutamine¶

Oletame, et me töötame mitme mooduliga. Kuna Pythonis saab programmi käivitada meelevaldselt valitud moodulist (pole määratud interpreteerimise alguspunkti) siis on tekkinud tava kirjutada moodulit ja selles olevat peaprotseduuri mida nimetatakse tavaliselt main järgmiselt:

from <moodul> import func1

def func2():  # Seda funktsiooni saad kasutada kolmandas moodulis.
    pass


def main():  # Siin teed funktsioonidega midagi...
    func1()  
    func2() 


if __name__ == '__main__':
    main()

Kasutatud if-tingimus tagab selle, et programmi peaprotseduur main käivitub ainult juhul kui interpreteerime algab sellest moodulist kus mainitud if-blokk asub. Eelöeldu töötab kuna moodulile kust alustatakse interpreteerimine omistatakse taustal spetsiaalsele muutujale __name__ väärtuseks '__main__' (andmetüübis sõne).

Peaprotseduur on protseduur millele pannakse tavaliselt nimeks main ning mis on kaitstud eelmises lõigus mainitus nimevalvuriga või nimehalduriga. Peaprotseduuri tööks on juhtida kogu programmi töövoogu ja kutsuda välja kõik teised kasutatavad protseduurid, funktsioonid, muutujad ja klassid.

Imporditud mooduli puhul määratakse selle mooduliga seotud spetsiaalsele muutujale __name__ väärtuseks mooduli enda nimi sõne formaadis.

Näide: Spetsiaalse muutuja __name__ väärtuse järelvaatamiseks saame kasutada näiteks eelnevalt kasutatud moodulit moodulFunkid mis sisaldab protseduuri namefunc. Protseduur namefunc on sisu on järgmine:

def namefunc():
    '''Protseduur mis prindib lause
print('\nmoodulFunkid spetsiaalne nimi __name__ =', __name__)
'''

    print('\nmoodulFunkid spetsiaalne nimi __name__ =', __name__)

In [10]:
import moodulFunkid

print(moodulFunkid.__name__)  # Muutuja __name__ järgivaatamine

moodulFunkid.namefunc()

print('\nSelle mooduli spetsiaalne nimi __name__ =', __name__)
moodulFunkid

moodulFunkid spetsiaalne nimi __name__ = moodulFunkid

Selle mooduli spetsiaalne nimi __name__ = __main__

Küsimus: Mis juhtub kui me alustame interpreteerimist moodulist moodulFunkid?

Mooduli DocString ja mooduli dokumenteerimine¶

Vastavalt PEP 8-le (Style Guide for Python Code) peab ka mooduleid dokumenteerima. Kasuta PEP 257 (Docstring Conventions) soovitusi. Mooduli DocString kirjutatakse mooduli algusesse. Kasuta mitmerealist sõnet (multiline string) kujul:

"""Pealkiri

Optional plotz says to frobnicate the bizbaz first.
Sisu kirjuta siia. Eelmine rida on näide.
"""

Mooduli DocStringi järgivaatamiseks kasuta sama lähenemist mis muude objektide abiinfo ja dokumentatsiooni lugemisel:

In [11]:
import moodulFunkid

#help(moodulFunkid)  # vt. iseseisvalt, liiga pikk väljatrükk.

# Konsoolis töötab ka nii:
moodulFunkid?
Type:        module
String form: <module 'moodulFunkid' from '/Users/dmitrikartofelev/Documents/WORK Teaching/YFX0500/Loeng 10/moodulFunkid.py'>
File:        ~/Documents/WORK Teaching/YFX0500/Loeng 10/moodulFunkid.py
Docstring:  
Moodul moodulFunkid
Moodul sisaldab kursuse jooksul loodud funktsioone 
mis on loodud õpilaste poolt.
Funktsiooni on kokku kogutud ja dokumenteeritud 
tudengite abiga, seega ei pruugi korralikult töötada.

Taustal on DocStringi sisu seotud spetsiaalse muutujanimega __doc__, seega:

In [12]:
print(moodulFunkid.__doc__)
Moodul moodulFunkid
Moodul sisaldab kursuse jooksul loodud funktsioone 
mis on loodud õpilaste poolt.
Funktsiooni on kokku kogutud ja dokumenteeritud 
tudengite abiga, seega ei pruugi korralikult töötada.

Näide heast ja halvast modulaarsusest¶

Allolevate koodinäidete taga on programm mis kodeerib "poomise" mängu inglisekeelse versiooni. Mängus "poomine" püüab kasjutaja talle teadmata sõna täht tähe haaval ära arvata. Programmis valib kasutaja mängu keerukuse ning üritab ennustada sõna, mis on valitud juhuslikult ette antud nimekirjast.

Halb näide programmist "poomine":

import random as r

words_easy = ["table", "star", "root", "flat", "turtle", "fish", "goat"]
words_hard = ["dragonborn", "monosaccharide", "disposable", "environment"]

difficulty = input("Please select difficulty('h' for 'hard' and 'e' for 'easy'): ")

if difficulty == 'h':
    words = words_hard
elif difficulty == 'e':
    words = words_easy
else:
    raise ValueError("Not allowed input.")

word = r.choice(words)
guesses = len(word)
guess_word = ["-"] * len(word)

while True:
    print(f"\nGuesses left: {guesses}")
    print(f"The word is: {''.join(guess_word)}")

    char = input("Please enter single character: ")
    if char in word:
        for i in range(len(word)):
            guess_word[i] = char if word[i] == char else guess_word[i]
    else:
        print(f"The character {char} is not in the word!")
        guesses -= 1

    if "".join(guess_word) == word:
        print("\nYou won!")
        print(f"The word is: {word}")
        break

    if guesses == 0:
        print("\nYou lost!")
        print(f"The word was {word}")
        break

Hea näide programmist "poomine":

import random as r


def select_difficulty(words_hard, words_easy):

    difficulty = input("Please select difficulty ('h' for 'hard' and 'e' for 'easy'): ")

    if difficulty == 'h':
        words = words_hard
    elif difficulty == 'e':
        words = words_easy
    else:
        raise ValueError("Not allowed input.")

    return words


def update_word(word, guess_word, char):

    for i in range(len(word)):
        if word[i] == char:
            guess_word[i] = char


def track_game_state(word, guess_word, guesses):

    if "".join(guess_word) == word:
        print("\nYou won!")
        print(f"The word is: '{word}'")
        return True

    if guesses == 0:
        print("\nYou lost!")
        print(f"The word was '{word}'")
        return True

    return False


def game(words_hard, words_easy):

    words = select_difficulty(words_hard, words_easy)

    word = r.choice(words)
    guesses = len(word)
    guess_word = ["-"] * len(word)

    finished = False

    while not finished:
        print(f"\nGuesses left: {guesses}")
        print(f"The word is: {''.join(guess_word)}")

        char = input("Please enter single character: ")

        if char not in word:
            print(f"The character '{char}' is not in the word!")
            guesses -= 1
        else:
            update_word(word, guess_word, char)

        finished = track_game_state(word, guess_word, guesses)


def main():

    easy = [
        "table", "star", "root", "flat", 
        "turtle", "fish", "goat"
    ]

    hard = [
        "dragonborn", "monosaccharide", 
        "disposable", "environment"
    ]

    game(words_hard=hard, words_easy=easy)


if __name__ == '__main__':
    main()

Pythoni standardteegid¶

Lausendit import kasutame ka Pythoni standardteekide ja moodulite ning kolmandate osapoolte teekide ja moodulite importimiseks.

Info Pythoni standardteekide kohta: https://docs.python.org/3/library/index.html

Valik olulisemaid Pythoni standardteeke:

  • string, re
  • datetime
  • collections
  • types
  • pprint
  • math
  • decimal
  • random, fractions
  • itertools
  • os.path
  • glob
  • shutil
  • pickle, shelve
  • zlib, zipfile, ...
  • csv
  • os
  • time
  • argparse
  • getpass
  • ctypes
  • threading
  • multiprocessing, subprocess
  • html, xml
  • urllib
  • sys
In [13]:
import math

print(dir(math))
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'cbrt', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'exp2', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
In [14]:
type(math)
Out[14]:
module
In [15]:
print(math.pi)
print(math.e)
3.141592653589793
2.718281828459045
In [16]:
math.exp(1) == math.e
Out[16]:
True
In [17]:
math.cos(0)
Out[17]:
1.0
In [18]:
math.sin(2 * math.pi)  # NB! Numbrilist määramatust, sin(2pi) = 0.
Out[18]:
-2.4492935982947064e-16

Kolmandate osapoolte teegid ja moodulid¶

Avalikuks kasutamiseks loodud moodulid on indekseeritud ja vabalt kautatamiseks allalaetavad. Veebisaidilt "Python Package Index" (PyPI) https://pypi.org/ leiab eelmainitud tarkvara. Lisaks sisaldab see sait infot ja juhiseid ka enda kirjutatud tarkvara avalikustamise ja jagamise kohta.

No description has been provided for this image
Joonis 4. Loetletud teekide ja projektide logod.

Valik olulisemaid teeke (Anacondaga vaikimisi kaasas) millede logod on kujutatud Joonisel 4:

  • NumPy — andmete esitus massiividena, https://numpy.org
  • SciPy — teadusarvutuste algoritmid, https://www.scipy.org
  • matplotlib — andmete analüüs ja visualiseerimine, https://matplotlib.org
  • pandas — andmete salvestus ja analüüs, https://pandas.pydata.org
  • IPython — interaktiivne andmete analüüs, https://ipython.org
  • SymPy — sümbolarvutus, computer algebra system (CAS) või symbolic algebra system (SAS), https://www.sympy.org/en/index.html
In [19]:
import numpy as np

type(np)
Out[19]:
module
In [20]:
print(np.pi)
print(np.e)
3.141592653589793
2.718281828459045
In [21]:
np.exp(1) == np.e
Out[21]:
True
In [22]:
np.cos(0)
Out[22]:
1.0
In [23]:
np.sin(2 * np.pi)  # NB! Numbrilist määramatust, sin(2pi) = 0.
Out[23]:
-2.4492935982947064e-16

Pythoni distributsiooni haldamine, pip, conda¶

Eelmises lõigus mainitud tarkvara allalaadimiseks ja installeermiseks kasutame spetsiaalselt selleks loodud Pythoni installatsiooniga kaasas olevat tarkvara.

Konsooli käsud:

PyPI tavalises konsoolis

  • pip
  • pip -h, abimaterjal (help)
  • pip list, instaleeritud teekide ja moodulite nimistu ja versioonid
  • pip install <nimi>, instaleeri uus teek/moodul
  • pip uninstall <nimi> eemalda instaleeritud teek/moodul

Anaconda konsoolis

  • conda
  • conda -h, abimaterjal (help)
  • conda list, instaleeritud teekide ja moodulite nimistu ja versioonid
  • conda install <nimi>, instaleeri uus teek/moodul
  • conda uninstall <nimi> või conda remove <nimi>, eemalda instaleeritud teek/moodul

Anaconda konsoolis töötavad ka pip käsud. Kui arvutisse on installeeritud Anaconda distributsioon siis on soovitatav kasutada ainult conda käske.

















☻   ☻   ☻