Muutuja ja viitamine

Üldiselt saab suure osa programmeerimisest ära teha selliselt, kui vaadelda muutujat kui väärtust. Juhul kui loome muutuja, millel on nimi, on sellel muutujal on väärtus. Kui koodis kasutame muutujat, kasutame selle koha peal väärtust.

Tegelikult on aga muutuja tööpõhimõte natuke mitmekülgsem. See sõltub suuresti ka programmeerimiskeelest. Vaatleme siin kõigepealt erinevaid levinud tehnikaid ning seejärel kirjeldame, kuidas Pythonis muutuja toimib.

Muutuja kui viit

Selleks, et paremini esile tuua viida toimimist, kasutame funktsiooni näidet. Siin on toodud üks programmikood, mis on inspireeritud Pythonist, aga tegelikult ei ole päris programmeerimiskeeles (pseudo-kood):

def add_one(number):
    number = number + 1

def main():
    value = 10
    add_one(value)
    print(value)  # => 11

Eelneva koodinäite mõte on see, et kui funktsioonile add_one antakse kaasa argument, siis see on viide mällu. Parameetri muutmisel muudetakse mälu sisu. Ehk siis value (funktsioonis main) ja number (funktsioonis add_one) viitavad samasse kohta mälus. Kui ühte muudetakse, muutub ka teine.

Sellist käitumist nimetatakse pass by reference. Seda on hea kasutada olukordades, kus andmete kopeerimine funktsiooni jaoks on ebaefektiivne. Näiteks kui meil on suur hulk andmeid, siis on kiirem anda kaasa viide juba mälus olevatele andmetele kui hakata koopiat tegema funktsiooni jaoks (vt järgmist peatükki).

Muutuja kui väärtus

Vaatame sarnast koodi olukorras, kus funktsioonile antakse edasi väärtus:

def add_one(number):
    number = number + 1

def main():
    value = 10
    add_one(value)
    print(value)  # => 10

Antud koodinäite puhul antakse add_one funktsiooni edasi väärtus 10. Funktsiooni sees liidetakse väärtusele 1 juurde ja salvestatakse muutujasse number. Aga loodav muutuja on kohalik muutuja ning ei mõjuta kuidagi välist muutujat (value). Seepärast on value väärtus lõpus endiselt 10.

Sellist käitumist nimetatakse pass by value.

Muutuja Pythonis

Pythonis kasutatakse muutuja funktsiooni andmisel omistamise reegleid (pass by assignment). Funktsiooni parameeter on muutuja, kuhu omistatakse kaasa pandud väärtus. Selleks, et teada, kuidas see täpsemalt toimib, peame vaatama üldiseid muutujale väärtuse omistamise reegleid.

Muutuja väärtustamise puhul võrdusmärgi vasakpoolne sihtmärk on nimetus ja parempoolne väärtus on objekt. Ehk x = 2 korral x on nimi ja 2 on objekt. Kui nimi on juba seotud mõne objektiga (eelnevalt on x muutujasse omistatud näiteks 1), siis x seotakse nüüd uue väärtusega (vana seos kaob ära).

Pythonis hoitakse lihtsamaid (muutumatud, immutable) objekte mälus vaid ühekordselt. Kui näiteks nii x kui y muutujatesse määratakse väärtus 2, siis tegelikult on mälus üks objekt 2 ja mõlemad (nii x kui y) viitavad sellele objektile. Kui sellist objekti 2 pole, siis see luuakse. Sisemiselt hoitakse sõnastikku selleks, et nimi ja objekt vastavusse viia.

Muutumatu objekt, nagu ka nimi ütleb, ei saa muutuda. Kui mälus on 2, siis see ei saa kuidagi muutuda 3-ks. Avaldis x + 1 võtab x praeguse väärtuse, liidab sellele 1 juurde ning saadakse tulemus (objekt 3). Kui toimub omistamine x = x + 1, siis arvutatakse välja parem pool (saadakse objekt 3) ja nüüd kirjutatakse x nime taga olev viide ümber nii, et ta viitab 3 peale.

Vaatame koodinäidet. Pythonis saab kasutada funktsioon id(), mis tagastab mäluaadressi (sõltuvalt Pythoni implementatsioonist ei pruugi see tingimata mäluaadress olla, aga garanteeritud on see, et sama objekt mälus tagastab sama tulemuse). Sellega on võimalik jälgida, mis toimub näiteks muutujale väärtuse liitimisel:

x = 2
print(id(x))  # 9789024
x = x + 1
print(id(x))  # 9789056
print(id(3))  # 9789056

Algselt oli muutujal x üks mäluaadress, pärast liitmist viitab muutuja väärtusele 3 ja see paikneb mälus teises kohas (id() tagastab teise tulemuse). Võrdleme seda lõpus väärtusega, mis saame otse objekti 3 pärides.

Muutuvad (täpsemalt muudetavad ehk mutable) objektid on need, mille puhul on võimalik nende sisu mälus muuta. Näiteks on järjend (list) selline andmetüüp:

names = ["jack", "mary"]
print(id(names))  # 140578104131264
names.append("robb")
print(id(names))  # 140578104131264
print(names)  # ['jack', 'mary', 'robb']

Eelmise näite korral luuakse muutuja names, mis hakkab viitama mälus loodud järjendile, kus on kaks elementi sees. Kui vaatame id() funktsiooniga selle names muutuja asukohta, siis näeme, et see ei muutu ka peale elemendi lisamist. Ehk siis järjend on muudetav andmetüüp mis tähendab, et olemasolevaid andmeid mälus saab muuta.

Muutumatu (immutable) andmetüüp

Muutumatu andmetüüp on selline, mille puhul väärtust mälus ei ole võimalik muuta. Kui muutujasse on soov saada uut väärtust, siis seda saab teha vaid väärtust üle kirjutades: x = x + 1 puhul x hakkab edaspidi viitama teisele objektile mälus.

Muutumatud andmetüübid Pythonis:

  • täisarv (integer, int)

  • reaalarv (float, float)

  • tõeväärtus (boolean, bool)

  • sõne (string, str)

  • ennik (tuple, tuple)

Muudetav (mutable) andmetüüp

Muudetava andmetüübi korral on objekti sisu võimalik mälus muuta.

Muudetavad andmetüübid Pythonis:

  • järjend (list, list)

  • hulk (set, set)

  • sõnastik (dictionary, dict)

  • kõik programmis loodavad andmetüübid (klassid)