Arvutiteaduse instituut
  1. Kursused
  2. 2025/26 sügis
  3. Programmeerimine (LTAT.03.001)
EN
Logi sisse

Programmeerimine 2025/26 sügis

  • Üldinfo
  • 1. Muutuja ja avaldis
  • 2. Tingimuslause
  • 3. Funktsioon
  • 4. Korduslause
  • 5. Sõned. Lihtsam failitöötlus
  • 6. Kontrolltöö 1
  • 7. Järjend
  • 8. Järjend 2
  • 9. Kahekordne tsükkel. Failitöötlus
  • 10. Andmestruktuurid
  • 11. Andmestruktuurid 2
  • 12. Kontrolltöö 2
  • 13. Objektorienteeritud programmeerimine
  • 14. Objektorienteeritud programmeerimine 2
  • 15. Rekursioon
  • 16. Kordamine. Projektide esitlused
  • Viiteid
  • Silmaringimaterjalid

Arhiveeritud esialgsed silmaringimaterjalid

  1. Standardteek ja moodulid
  2. Rakendusliidesed
  3. Regulaaravaldised
  4. Andmebaasid
  5. Veebirakenduste loomine
  6. Objektorienteeritud programmeerimine
  7. Graafiliste mängude loomine
  8. Keerulisemad Pythoni võimalused
  9. Võistlusprogrammeerimine
  10. Veebisisu parsimine
  11. Pilditöötlus
  12. Pythoni siseehitus
  13. Helitöötlus
  14. Kodeerimine ja krüpteerimine
  15. NumPy
  • Materjalid

Pythoni siseehitus

See peatükk püüab anda põhjalikuma ülevaate sellest, kuidas üldiselt programmikood arvutis käima pannakse ja mis juhtub Pythoni koodiga, kui see käivitatakse. Uurime erinevaid võimalusi, kuidas käivitada Pythoni programme arvutites, kus Pythoni töövahendid puuduvad. Samuti vaatame, milline näeb välja Pythoni baitkood, kuidas seda analüüsida ja kuidas on Python seotud programmeerimiskeelega C.

Ettevalmistus

Sellest materjalist saad paremini aru, kui oled läbinud õpikus klasside ja objektide peatüki. Siin peatükis mainitakse erinevaid programme ja Pythoni lisamooduleid, mille hulgast võib valida, millist kasutada, sest aja jooksul võivad mõned neist aeguda. Teisisõnu, kontrolli vajadusel, kas sinu valitud programm või moodul toimib su Pythoni versiooniga!

Kompileerimine

Kompileerimine ehk transleerimine tähendab ühes arvutikeeles kirjutatud koodi tõlkimist teise arvutikeelde. Tavaliselt mõeldakse sellega programmeerija kirjutatud programmikoodi tõlkimist masinkoodiks, mida protsessor saab täitma hakata.

Tänapäeva programmid kasutavad töö ajal kasvõi sisendi/väljundi tarvis või mälule ligipääsemiseks operatsioonisüsteemi abi, aga iga operatsioonisüsteem on omamoodi ehitatud. Seetõttu peab iga operatsioonisüsteemi jaoks programmi eraldi kompileerima. Näiteks Linuxi operatsioonisüsteem ei oska Windowsi EXE-faile käivitada. Lisaks erinevad protsessorite arhitektuurid, nii et ka nende jaoks peab tihti programmi eraldi kompileerida.

Kompileerimise protsessi saab jagada sammudeks. Tüüpiline kompilaator tegutseb järgnevalt:

  1. Leksiline analüüs: Kõigepealt eraldatakse programmeerija kirjutatud tekstist võtmesõnad, muutujanimed, tehtemärgid, erinevad literaalid (sõned, arvud, järjendid jne...).
  2. Süntaksianalüüs: Kontrollitakse, et eelmise sammu väljund oleks "grammatiliselt" korrektne. Kui ei ole, visatakse kasutajale süntaksiviga!
  3. Semantiline analüüs: Vaadatakse üle, et programm oleks tähenduslikult korrektne ehk kõik operatsioonid oleks selles programmeerimiskeeles lubatud.
  4. Peale semantilist analüüsi tehakse programmeerimiskeele käskudest madalama taseme instruktsioonid ehk natuke vähem abstraktne programmikood.
  5. Saadud vahepealne kood optimiseeritakse ehk mingid käsud lihtsustakse, eemaldatakse või tõstetakse ümber. Optimisatsioonid võivad olla ka operatsioonisüsteemist või protsessorist sõltuvad.
  6. Viimase sammuna luuakse vastava masina (protsessoriarhitektuuri ja operatsioonisüsteemi) jaoks masinkood, mida saab käivitada.

Veidi lähemalt saad iga sammu kohta lugeda siit: https://www.tutorialspoint.com/compiler_design/compiler_design_phases_of_compiler.htm

Interpreteerimine

Masinkoodiks kompileerimata programmikoodi käivitamist nimetatakse interpreteerimiseks. See tähendab, et interpretaatoriks või virtuaalmasinaks kutsutav programm analüüsib ja jooksutab koodi käske ükshaaval. Põhimõtteliselt võib interpretaator tsüklid läbi käia näiteks nii, et igal tsüklisammul loetakse tsüklikehas olevad käsud uuesti. Käske ei kompileerita, vaid interpretaator jooksutab ise käsule vastavad (eelkompileeritud) protsessorikäsud.

Lähenemise eelis on see, et programmi ei pea iga platvormi jaoks eraldi kompileerima ja kompileerimisprotsess enne käivitamist võtab vähem aega. Seetõttu on näiteks Pythoni käske võimalik interaktiivselt käsurealt kiiresti käivitada. Samas peab koodi jooksutamiseks olema installitud vajalik tarkvara ja programmi täitmine on üldjuhul aeglasem.

Vahepealsed variandid

Mõned kompilaatorid ei tee koodi otse protsessori jaoks arusaadavaks masinkoodiks vaid veidi kõrgema taseme käskudeks, mida nimetatakse baitkoodiks. Põhimõtteliselt jäetakse kompileerimise 5. ja 6. samm tegemata ja baitkood antakse protsessori asemel vastava baitkoodikeele virtuaalmasinale. Virtuaalmasin võib seejärel kompileerida selle keele masinkoodiks või käituda selle interpretaatorina või teha midagi vahepealset.

Käitusaegne kompileerimine

Käitusaegne ehk JIT (just-in-time) kompileerimine on teatud kompromiss kompileerimise ja interpreteerimise vahel. Baitkood tehakse valmis enne käivitamist ja käivitamise hetkel kompileeritakse see masinkoodiks, kuigi mingit osa tavaliselt ka interpreteeritakse. Nii käitub näiteks Java standardne virtuaalmasin (JVM). Käitusaegse kompileerimise puhul võib virtuaalmasin ka programmi analüüsida ja töötamise ajal vastavalt vajadusele teatud osi kompileerida.

Python

Programmeerimiskeele Python vaikekompilaator ning virtuaalmasin on CPython, mis ise on C-keeles kirjutatud programm. CPython kompileerib Pythoni käsud endale arusaadavaks Pythoni baitkoodiks ja interpreteerib seda.

Alternatiivsed Pythoni kompilaatorid

Lisaks CPythonile on kirjutatud ka teisi Pythoni kompilaatoreid ja interpretaatoreid, mida on soovi korral võimalik kasutada. Need võivad samas tõlkida koodi veidi erinevaks baitkoodiks ja seega ei pruugi nad alati olla sama töökindlad ega töötada kõigi lisateekidega.

Nuitka

Nuitka on Pythonis kirjutatud Pythoni kompilaator, mida on võimalik installida ka käsurealt pip abil: pip install nuitka. Nuitka transleerib Pythoni koodi C koodiks ning teeb selle C kompilaatori abil masinkoodiks. Viimane versioon lubab korrektselt töötada ka kõigi lisateekidega. Käsk python -m nuitka programmi_nimi.py --onefile teeb valmis käivitatava programmi, mis on mahutatud ühe faili sisse. Kui arvutist mõnda vajalikku tööriista (näiteks C või C++ kompilaatorit) ei leita, siis küsib Nuitka kompileerimise ajal viisakalt luba selle allalaadimiseks. Selle kasutamise kohta võid lähemalt lugeda: https://nuitka.net/doc/user-manual.html

PyPy

Üks populaarne interpretaator on PyPy, mis kasutab lisaks käitusaegset kompileerimist, nii et programmikood jookseb paljudel juhtudel märgatavalt kiiremini, kui Pythoni vaikeinterpretaatoriga. Samas ei saa sellega kompileerida koodi selliseks failiks, mida saaks jagada ja ilma interpretaatorita käivitada. Rohkem infot leiab PyPy kodulehelt: www.pypy.org.

Brython (Browser Python)

Et Pythoni koodi veebilehtedel jooksutada, on võimalik kasutada kompilaatorit nimega Brython, mis tõlgib Pythoni koodi keelde Javascript. Installida saab seda samuti pip kaudu, juhised alustamiseks leiad siit: https://pypi.org/project/brython/. Projekti arendajad loodavad sellega välja vahetada Javascripti, aga enamikke Pythoni lisateeke Brythonis kasutada ei saa.

Pakkimine

Lisaks kompileerimisele on Pythoni programme võimalik jagada ka teisiti. Üks võimalus on pakkida oma kood, Pythoni interpretaator ning vajalikud teegid ja lisafailid kokku üheks programmiks. Seda on võimalik teha sellise tööriistaga nagu pyinstaller või cx_Freeze - kumbagi saab installida vastava pip install käsuga.

Pyinstaller on käsureal kasutatav käsuga pyinstaller --onefile --clean programmi_nimi.py. Parameetriga --onefile pakitakse kõik ühte faili. cx_Freeze jaoks tuleb teha Pythoni skript, näiteks selline:

from cx_Freeze import setup, Executable

# Moodulid, mida programm ei kasuta, võib välja jätta "excludes" abil
build_exe_options = {"excludes": ["tkinter"]}

setup(
    options={"build_exe": build_exe_options},
    executables=[Executable("programmi_nimi.py")],
)

Seejärel tuleb see käivitada käsurealt käsuga python skript.py build, mis loob kausta build. Sealt leiab nüüd oma pakitud käivitatava programmi. Skripti saab täiendada vastavalt vajadusele, loe parameetrite kohta cx_Freeze dokumentatsioonist.


Allikas: xkcd

Pythoni baitkood

Järgnevates näidetes vaatamegi peamiselt, mismoodi tegutseb Pythoni koodiga selle ametlik implementatsioon CPython. Kasutame sisseehitatud moodulit dis, mille abil saab mingi funktsiooni baitkoodi osadeks võtta ja loetavalt väljastada:

def liida(a, b):
    print("Liidan!")
    return a+b
>>> import dis
>>> dis.dis(liida)
 2            0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('Liidan!')
              4 CALL_FUNCTION            1
              6 POP_TOP

 3            8 LOAD_FAST                0 (a)
             10 LOAD_FAST                1 (b)
             12 BINARY_ADD
             14 RETURN_VALUE

Igale koodireale vastab üldjuhul mitu käsku:

  1. Tabeli vasakus veerus on kirjas reanumbrid, millele järgmised baitkoodi käsud vastavad.
  2. Teises veerus olevad arvud on positsioonid, kus vastav käsk baitkoodis asub.
  3. Keskel on baitkoodikäsu nimi.
  4. Neljandas veerus võib olla käsu argument (täpsemini indeks, mis viitab selle asukohale mälus).
  5. Paremal võib olla argumendi väärtus või muutuja nimi.

Kui on vaja seda käskude jada programmis kasutada, saab kasutada funktsiooni get_instructions, mis tagastab needsamad andmed veidi teisel kujul. Igale käsule vastab klassi Instruction isend, mis hoiab käsu kohta infot isendimuutujatena.

>>> käsud = list(dis.get_instructions(liida))
>>> for käsk in käsud:
        print(käsk)

Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=0, starts_line=5, is_jump_target=False)
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval='Liidan!', argrepr="'Liidan!'", offset=2, starts_line=None, is_jump_target=False)
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=4, starts_line=None, is_jump_target=False)
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=6, starts_line=None, is_jump_target=False)
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='a', argrepr='a', offset=8, starts_line=6, is_jump_target=False)
Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='b', argrepr='b', offset=10, starts_line=None, is_jump_target=False)
Instruction(opname='BINARY_ADD', opcode=23, arg=None, argval=None, argrepr='', offset=12, starts_line=None, is_jump_target=False)
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=14, starts_line=None, is_jump_target=False)

Hetkel piisab, kui teada nende isendite järgmiseid atribuute:

  • opname - Baitkoodikäsu nimi.
  • opcode - Käsu arvkood.
  • argval - Käsu argumendi väärtus.
  • offset - Positsioon, kus käsk baitkoodis asub.
  • is_jump_target - Ütleb, kas programm võib täitmise ajal hüpata selle käsuni kuskilt mujalt (nt. if-lausest).

Paljud baitkoodikeeled, sealhulgas Pythoni oma, ei anna käskudele kõiki andmeid edasi argumentidega, vaid vahemäluks on virnataoline andmestruktuur (pinu, ingl. stack). Üks käsk paneb mingi väärtuse virna peale ja järgmine käsk võib sealt pealmise väärtuse ära kasutada. Ka ülalolevas näites toimub kahe arvu liitmine nii, et käskudega LOAD_FAST lisatakse pinusse kaks arvu. Kolmas käsk, liitmistehe BINARY_ADD, võtab ükshaaval kaks arvu pinust ära ja paneb sinna tagasi nende summa.

Mida erinevad baitkoodikäsud teevad?

KäskArgument (argval)Kirjeldus
LOAD_CONSTmingi konstantLisab pinusse oma argumendiks oleva konstandi.
LOAD_GLOBALmuutuja nimiLisab pinusse (globaalse) muutuja väärtuse (nt. sisseehitatud funktsiooni nime).
LOAD_FASTmuutuja nimiLisab pinusse (lokaalse) muutuja väärtuse.
LOAD_NAMEmuutuja nimiLisab pinusse muutuja väärtuse, otsib kõigepealt lokaalsete, siis globaalsete hulgast.
STORE_NAMEmuutuja nimiVõtab pinust väärtuse ja salvestab mälus vastavasse muutujasse.
POP_TOP-Võtab pinust väärtuse (kasutatakse kui on lihtsalt vaja pealmine väärtus eemaldada).
BINARY_TRUE_DIVIDE-Võtab pinust jagaja, seejärel jagatava ja paneb pinusse jagatise.
BINARY_SUBTRACT-Võtab pinust vähendaja, seejärel vähendatava ja paneb pinusse vahe.
BINARY_SUBSCR-Võtab pinust väärtuse (ütleme i), muutuja nime (ütleme x) ja paneb pinusse x[i].
BUILD_LISTväärtuste arvVõtab pinust määratud arvu väärtusi ja lisab pinusse neist tehtud järjendi.
LIST_EXTEND-Võtab pinust väärtuse, seejärel järjendi ja kutsub järjend.extend(väärtus).
CALL_METHODargumentide arvVõtab pinust argumentide väärtused, meetodi nime, objekti nime ja kutsub vastava objekti meetodit. Meetodi tagastatud väärtuse lisab pinusse.
CALL_FUNCTIONargumentide arvVõtab pinust argumentide väärtused, funktsiooni nime ja kutsub funktsiooni koos selle argumentidega. Funktsiooni tagastatud väärtuse lisab pinusse.
JUMP_FORWARDpositsioonHüppab programmis edasi määratud kohani.
POP_JUMP_IF_TRUEpositsioonVõtab pinust väärtuse. Kui selle tõeväärtus on tõene, viib programmi täitmise antud positsioonil asuva käsuni.

Kõikide võimalike baitkoodikäskude kirjeldused leiab mooduli dis dokumentatsioonist.

Baitkoodifailid

Pythoni koodi on huvi korral võimalik käsitsi kompileerida PYC-baitkoodifailiks mooduli py_compile abil, aga selliste failide käivitamiseks on siiski vaja kasutada interpretaatorit. PYC-faili võib tekstifailina avada, aga see on "toorel" baidijadaks kodeeritud kujul ja käsud on kirjutatud nende arvkoodide abil. Sarnase esituse on võimalik kätte saada ka nt. funktsiooni salajase atribuudi __code__ kaudu:

>>> liida.__code__.co_code
b't\x00d\x01\x83\x01\x01\x00|\x00|\x01\x17\x00S\x00'

Oma interpretaatori kirjutamine

Et paremini aru saada, kuidas programme käsuhaaval täidetakse, võiksime ise proovida teha programmi, mis suudab täita mingit piiratud hulka baitkoodi käske. Et ei peaks teksti parsimise ja süntaksi analüüsimisega tegelema, kasutame Pythoni baitkoodi, mille saab eelnevalt vaadatud käskude abil mugaval kujul kätte.

Kuidas seda teha?

Võiksime luua sõnastiku muutujate jaoks ja vahemälu jaoks pinu. Iga käsu töötlemiseks võib teha ühe if-haru. Kõikvõimalike käskudega hetkel arvestama ei pea, näiteks käske LOAD_GLOBAL, LOAD_FAST, STORE_GLOBAL, STORE_FAST ei kohta väljaspool enda defineeritud funktsioone.

Allolevas näites võib jälle märgata, et käsud on kirjas klassi Instruction isendites ja isendimuutuja argval sisaldab käsuargumendi väärtust. Kui argument on muutuja nimi, siis on see sõne kujul ja peame võtma mälust muutuja väärtuse. Enda defineeritud muutujaid saame meeles pidada, aga kuidas leida nimele vastav sisseehitatud funktsioon või muutuja? Võiksime käsitsi need kõik eelnevalt sõnastikku panna, aga natuke mugavamalt saab ka. Funktsioon getattr tagastabki just nime põhjal mingist objektist või moodulist etteantud nimega muutuja. Sisseehitatud funktsioonid on juhuslikult veel eraldi olemas moodulis builtins, nii et kasutame selle teadmise ära. Nüüd saame nt. funktsiooni print kätte selle nime järgi: fn = getattr(builtins, "print")

"Mujale hüppavate" käskude puhul on sihtkäsu positsiooniks märgitud sihtkäsu offset väärtus, üldjuhul on see täpselt kaks korda suurem kui selle käsu indeks instruktsioonide järjendis. Seega, kui muudame oma vaadeldava indeksi väärtuse vastavaks, saamegi sinna hüpata.

NB! Ainult konstantidega tehteid interpretaator arvutama ei pea, need on kompilaator juba ära teinud!

Mini-interpretaator

Kui tahame nüüd jooksutada programme, kus on kasutatud ainult lihttüüpi muutujaid, tingimuslauseid, omistamisi ja ühe argumendiga väljastuskäske, saaksime kirjutada midagi sellist:

### ### ### ### ### ### ### ### ### ### ### ###
###  Pythoni baitkoodi interpretaator 2.0   ###
### ### ### ### ### ### ### ### ### ### ### ###

import dis
import builtins

with open("programm.py", encoding="UTF-8") as f:
    programm = f.read()

käsud = list(dis.get_instructions(programm)) # Teeb ära kompileerimis- ja teisendusprotsessi
pinu = []
mälu = {}
indeks = 0

while indeks < len(käsud):
    käsk = käsud[indeks]

    # Lisa pinusse konstant
    if käsk.opname == "LOAD_CONST":
        pinu.append(käsk.argval)

    # Lisa pinusse muutuja väärtus
    elif käsk.opname == "LOAD_NAME":
        nimi = käsk.argval
        if nimi in mälu:
            väärtus = mälu[nimi]
        else:
            # Sisseehitatud muutuja
            väärtus = getattr(builtins, nimi)
        pinu.append(väärtus)

    # Salvesta pinust muutujasse väärtus
    elif käsk.opname == "STORE_NAME":
        nimi = käsk.argval
        väärtus = pinu.pop()
        mälu[nimi] = väärtus

    # Kutsu funktsioon
    elif käsk.opname == "CALL_FUNCTION":
        args = []
        if käsk.argval != 0:
            args.append(pinu.pop())

        fun = pinu.pop()
        val = fun(*args)
        pinu.append(val)

    # Hüppa edasi mingi muu käsuni
    elif käsk.opname == "JUMP_FORWARD":
        indeks = käsk.argval // 2
        continue

    # Hüppa mingi muu käsuni, kui tingimus ei kehti
    elif käsk.opname == "POP_JUMP_IF_FALSE":
        väärtus = pinu.pop()
        if not väärtus:
            indeks = käsk.argval // 2
            continue

    # Hüppa mingi muu käsuni, kui tingimus kehtib
    elif käsk.opname == "POP_JUMP_IF_TRUE":
        väärtus = pinu.pop()
        if väärtus:
            indeks = käsk.argval // 2
            continue

    # Viska pinust väärtus minema
    elif käsk.opname == "POP_TOP":
        pinu.pop()

    indeks += 1

Sisendina võib kasutada järgmist programmi:

tervitus = "Tere!"
a = 0; b = 4

print(tervitus)
if a:
    print("a ei ole 0.")
elif not b:
    print("b pole midagi tõeset.")
else:
    print("Else haru!")

print("Nägemiseni!")

Python ja C

Erinevalt Pythonist on C loodud pigem kompileeritavaks keeleks. C-s kirjutatud programmid on väga kiired osaliselt sellepärast, et nad ei paku vaikimisi niipalju mugavusi ning paljutki, mis Pythonis on iseenesestmõistetav, peab tegema käsitsi: Programmeerija peab muutujatele ise mäluruumi taotlema ning C programmid ei jälgi niivõrd seda, kas töö ajal tekib vigu või mis neid põhjustab. Python annab konkreetse veateate, kui programm püüab ligi pääseda 5-elemendilise järjendi 6. elemendile. C-keele massiivide puhul vaadatakse lihtsalt mälus sinna, kus loogiliselt asuks massiivi 6. element ja tagastakse misiganes väärtus sellel mäluaadressil hetkel asub. See viib tihti ootamatute tagajärgedeni ning vea põhjust võib olla raske leida.

Sisseehitatud funktsioonid

Kõik Pythoni sisseehitatud mooduli funktsioonid ja klassid ning mõned teised moodulid on CPythonis realiseeritud C-keeles. Kuna C-keeles kirjutatu on masinkoodiks kompileeritud, töötavad need kiiremini ning neid on mõistlik võimalikult palju ära kasutada. Näiteks selle asemel, et Pythoni tsükli abil täita järjend arvudega vahemikust [0,10),

arvud = [i for i in range(10)]

võib seda teha kahe sisseehitatud funktsiooni abiga:

arvud = list(range(10))

Kui kumbki dis abil lahti võtta, on samuti näha, et esimene variant teeb tublisti rohkem operatsioone Pythonis:

>>> dis.dis("[i for i in range(10)]")
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x000001CBE2AE4EA0, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (range)
              8 LOAD_CONST               2 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x000001CBE2AE4EA0, file "<dis>", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>
>>> dis.dis("list(range(10))")
  1           0 LOAD_NAME                0 (list)
              2 LOAD_NAME                1 (range)
              4 LOAD_CONST               0 (10)
              6 CALL_FUNCTION            1
              8 CALL_FUNCTION            1
             10 RETURN_VALUE

On näha, et kui kutsutakse välja mõni funktsioon (baitkoodis käsk CALL_FUNCTION), siis selle baitkoodi dis eraldi ei näita. See kuvatakse ainult siis, kui dis argumendiks anda moodul ja kui funktsioon pole kirjutatud C-keeles.

Rohkem sisseehitatud funktsioonide kasutusnäiteid leiad peatükist "keerulisemad Pythoni võimalused" ja täieliku nimekirja Pythoni dokumentatsioonist.

Pythoni ja C programmide liitmine

Leidub ka muid viise, kuidas C võimekust Pythoni koodis ära kasutada. Üks võimalus on kasutada kompileeritud programme Pythoni alamprogrammidena. C teekide täpsemaks kasutamiseks sobib moodul ctypes, mis lubab C programme ja teeke oma koodis moodulitena kasutada ning nende funktsioone välja kutsuda. See on aga omaette teema ja eeldab juba veidi rohkem C-keele tundmist.

Muutujad ja väärtused

Pythoni standardimplementatsioonis CPython on iga väärtus realiseeritud objektina (k.a. funktsioonid) ja iga muutuja viitab kohale mälus, kus vastav objekt asub. Iga objekti unikaalse mäluaadressi saab kätte käsuga id(muutuja_nimi). Mitu muutujat võivad sellegipoolest viidata samale mäluaadressile, nii et kummagi kaudu pääseb ligi samale objektile. Kuna muutujad on sisuliselt mäluaadressid, saavad ka funktsioonid parameetritena just aadressid, mitte väärtused. Omistamistehe Pythonis omistab muutujale tegelikult lihtsalt uue mäluaadressi.

Arvudega on seda hea demonstreerida. Võtmesõna is abil on võimalik kontrollida, kas muutujad viitavad samale kohale mälus:

>>> a = b = 257
>>> a is b
True
>>> b = 257
>>> a is b
False

Võrdusmärgi paremal poolel asuv avaldis loob mingi objekti ja paigutab selle arvutimälus kuhugi. Siin näites luuakse mällu täisarvuobjekt, mis kannab väärtust 257. Võrdusmärgiga omistatakse muutujatele a ja b selle mälupiirkonna aadress, nii et a ja b viitavad samal aadressil asuvale objektile. Pärast seda luuakse uus, sama arvulise väärtusega täisarvuobjekt, mis paigutatakse kuhugi mujale mälus ja muutujale b omistatakse see mäluaadress. Muutujad a ja b ei viita siis enam samale objektile.

Näide ei tööta väiksemate arvudega kui 257, sest väiksemaid arve hoitakse programmi töö ajal püsivalt mälus (teatud optimisatsioon). Iga kord kui muutujale omistatakse tehte tulemusena mõni väiksem arv, ei looda uut objekti, vaid antakse muutujale olemasolev mäluaadress:

>>> a = b = 5
>>> a is b
True
>>> b = 5
>>> a is b
True

Enesekontrolliküsimused

Ülesanded

1. Koosta analüsaator-programm, mis väljastab baitkoodi põhjal infot mingi Pythoni funktsiooni või programmi koodi kohta, näiteks:

  • Mitut funktsiooni see välja kutsub?
  • Kui palju kasutab sisseehitatud funktsioone?
  • Mitu for-tsüklit see sisaldab?
  • Mitu erinevat muutujat see loob?
  • Milliseid mooduleid see kasutab?

2. Täienda näites loodud Pythoni baitkoodi interpretaatorit, nii et see suudaks täita sellist programmi, kus on kasutatud lisaks:

  1. erinevaid muutujaid, tehteid ja sisseehitatud funktsioone (kergem).
  2. ka tsükleid või enda defineeritud funktsioone (raskem).

3. Kirjuta Pythoni baitkoodi dekompilaator, mis püüab taastada baitkoodi põhjal algse koodi.

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused