Programmi kompileerimine ja interpreteerimine
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 kompileerima.
Kompileerimise protsessi saab jagada sammudeks. Tüüpiline kompilaator tegutseb järgnevalt:
- Leksiline analüüs: Kõigepealt eraldatakse programmeerija kirjutatud tekstist võtmesõnad, muutujanimed, tehtemärgid, erinevad literaalid (sõned, arvud, järjendid jne...).
- Süntaksianalüüs: Kontrollitakse, et eelmise sammu väljund oleks "grammatiliselt" korrektne. Kui ei ole, visatakse kasutajale süntaksiviga!
- Semantiline analüüs: Vaadatakse üle, et programm oleks tähenduslikult korrektne ehk kõik operatsioonid oleks selles programmeerimiskeeles lubatud.
- Peale semantilist analüüsi tehakse programmeerimiskeele käskudest madalama taseme instruktsioonid ehk natuke vähem abstraktne programmikood.
- 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.
- 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.
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:
- Tabeli vasakus veerus on kirjas reanumbrid, millele järgmised baitkoodi käsud vastavad.
- Teises veerus olevad arvud on positsioonid, kus vastav käsk baitkoodis asub.
- Keskel on baitkoodikäsu nimi.
- Neljandas veerus võib olla käsu argument (täpsemini indeks, mis viitab selle asukohale mälus).
- 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äsk | Argument (argval) | Kirjeldus |
---|---|---|
LOAD_CONST | mingi konstant | Lisab pinusse oma argumendiks oleva konstandi. |
LOAD_GLOBAL | muutuja nimi | Lisab pinusse (globaalse) muutuja väärtuse (nt. sisseehitatud funktsiooni nime). |
LOAD_FAST | muutuja nimi | Lisab pinusse (lokaalse) muutuja väärtuse. |
LOAD_NAME | muutuja nimi | Lisab pinusse muutuja väärtuse, otsib kõigepealt lokaalsete, siis globaalsete hulgast. |
STORE_NAME | muutuja nimi | Võ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_LIST | väärtuste arv | Võ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_METHOD | argumentide arv | Võtab pinust argumentide väärtused, meetodi nime, objekti nime ja kutsub vastava objekti meetodit. Meetodi tagastatud väärtuse lisab pinusse. |
CALL_FUNCTION | argumentide arv | Võtab pinust argumentide väärtused, funktsiooni nime ja kutsub funktsiooni koos selle argumentidega. Funktsiooni tagastatud väärtuse lisab pinusse. |
JUMP_FORWARD | positsioon | Hüppab programmis edasi määratud kohani. |
POP_JUMP_IF_TRUE | positsioon | Võ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:
- erinevaid muutujaid, tehteid ja sisseehitatud funktsioone (kergem).
- ka tsükleid või enda defineeritud funktsioone (raskem).
3. Kirjuta Pythoni baitkoodi dekompilaator, mis püüab taastada baitkoodi põhjal algse koodi.