Imena in objekti

Čeprav bomo danes pogosto uporabili besedo "objekt", se še ne bomo zares pogovarjali o objektnem programiranju. Besedo pa vseeno potrebujemo: z njo bomo mislili na "tisto, kar shranimo v pomnilniku", recimo kakšno število, seznam, niz, datoteko (malo kasneje pa celo kakšno funkcijo ali kakšen modul). Objekte bomo risali tako, da jih bomo zaprli v pravokotnik; za kakšne vrste objekt gre, pa bo razvidno iz tega, kar bo pisalo v tem pravokotniku.

Objekti so torej tisto, kar je. Do objekta (shranjene številke, niza, seznama...), pridemo prek njegovega imena (x) ali pa, posredno, prek, recimo indeksiranja (seznam_otrok[2]).

Na slikah, ki jih bomo risali, bo desna stran vsebovala objekte, leva imena.

Spomniti se moramo še kaj pomeni prirejanje. Prirejanje ima na desni strani nek izraz. Rezultat izračuna tega izraza je nek objekt: če je na desni strani izraz 1 + 1, bo rezultat izračuna objekt 2. Na levi strani prirejanja je ime: prirejanje priredi objekt, ki ga naračunamo iz desne strani, imenu, ki smo ga napisali na levi.

Kaj naredi naslednji program?

a = 1 b = "Benjamin" c = 2 + a

Naredi, kar kaže slika na desni. Prva vrstica sestavi objekt 1 in ga priredi imenu a. Nato naredi niz "Benjamin" in ga priredi imenu b. V tretji vrstici se izraz na desni izračuna tako, da Python sestavi objekt 2, nato sešteje ta objekt in objekt, na katerega se nanaša ime a. Rezultat je objekt 3; prireditveni stavek ga priredi imenu c.

Čeprav smo doslej govorili o spremenljivkah (in bomo še, navada je železna srajca), bomo vsaj danes rekli: Python nima spremenljivk, vsaj ne v takšnem pomenu besede, kot smo jih morda navajeni iz drugih jezikov. Python ima samo imena za objekte.

Čemu tega doslej nismo omenjali? Ker ni bilo potrebno. Nič takšnega se ni dogajalo, da bi se morali ukvarjati s tem, kaj je zadaj. Zdaj, ko smo prišli do funkcij, in sploh, ko bomo kmalu prišli do objektov, pa si moramo nekatere stvari pojasniti. V preostanku zapiskov si bomo zato ogledali nekaj primer, ki so presenetljivi. To seveda niso stvari, v katere bi se človek vsakodnevno zaletel, temveč le izbrani primeri, ki dobro ilustrirajo, kako delujejo spremenljivke imena.

Še nekaj povejmo: Python nikoli ne dela kopij objektov. Kadar bomo mislili, da imamo dve kopiji istega objekta, bomo imeli v resnici le dvakrat isti objekt - razen, kadar se posebej potrudimo, da bi dobili kopijo. Pa še takrat bo kopija navadno plitva. Posebej globoko v to temo nima smisla riniti; kdor hoče vedeti več o tem v Pythonu, naj pogleda dokumentacijo modula copy. Podobno velja tudi za druge jezike: da lahko v resnici skopiramo objekt, mora biti bodisi "serializable" (C#, Python, JavaScript...), podpirati kloniranje (Java) ali pa mora jezik omogočati dovolj introspekcije (Python, JavaScript...).

Primer 0

Preden zares začnemo, se moramo le še dogovoriti, kako bomo risali sezname. Recimo, da napišemo

t = [1, 2, 3]
Dobili bomo, kar kaže slika na desni: tri objekte, 1, 2 in 3, ter četrti objekt, seznam, ki vsebuje te tri objekte. Takšno risanje je nepraktično, zato bomo številke risali kar naravnost v sezname, kot kaže spodnja slika.
t = [1, 2, 3]
Kadar bodo v seznamih kake druge stvari, pa bomo risali, tako kot je res - iz seznama bo vodila puščica na objekt (ali objekte), ki jih vsebuje.

Primer 1

Sestavimo tri sezname, t, u in v.

t = [1, 2, 3] u = t v = t[:]

Tri? V resnici imamo dva seznama. Prvi seznam sestavimo v prvi vrstici, tu dvomov ni. Kaj naredi druga vrstica? Pogleda izraz desni strani enačaja: tam piše t; izraz torej pravi "tisti seznam, ki smo ga poimenovali t. Torej je u isto kot t. Seznam, ki smo ga naredili v prvi vrstici, ima tako dve imeni, rečemo mu lahko t ali u. V tretji vrstici pa sestavimo nov seznam, v katerem so vsi elementi t-ja, od prvega do zadnjega; rezine pač vedno sestavljajo nove sezname. Temu, novemu seznamu smo dali ime v.

Vsi trije seznami so enaki.

>>> t == u True >>> t == v True Niso pa isti. Imeni t in u se nanašata na en in isti objekt, ime v pa na drugega. Ali se dve imeni nanašata na isto, lahko preverimo z operatorjem is; operator is je podoben operatorju ==, le da prvi preverja istost, drugi pa samo enakost. >>> t is u True >>> t is v False

(Takoj opozorimo: is ni zamenjava za ==. Nekateri študenti začnejo takoj, ko izvedo za is, navdušeno pisati pogoje, kot je if ime is "Benjamin". To včasih celo dela, ker Python izvaja neke zahrbtnosti; v resnici pa je ta, ki je to napisal, najbrž hotel reči if ime == "Benjamin". Ne zamenjujte is in ==!)

Zdaj seznamu u dodajmo še en element. Kaj dobimo?

u.append(4)
S tem, ko smo spreminjali u, smo spreminjali tudi t, saj gre za eno in isto reč. Seznam, ki ga imenujemo v, je ostal takšen, kot je bil.

Zdaj pa priredimo uju prazen seznam.

u = []

Tule je prvi kamen spotike za vse, ki niso bili dovolj pozorni, ko smo povedali, kaj je prirejanje. Ko napišemo u = [], ne spreminjamo vrednosti spremenljivke u, temveč dodeljujemo nek objekt imenu u. Razumite to in ste zmagali. Odtod naprej gre vse po istem kopitu.

Ko imenu u priredimo nov objekt, to ne vpliva na objekt, ki ga imenujemo t. Ostaja tak, kot je bil (saj ga nihče ni spreminjal) in tudi ime t se še vedno nanaša nanj.

Primer 2

Naredimo prazen seznam, poimenujmo ga e; potem naredimo seznam, ki vsebuje ta seznam.

e = [] t = [e]

Da sta e in ničti element t, t[0], res eno in isto, se hitro prepričamo.

>>> e is t[0] True

Kar bomo torej počeli z e, se bo zgodilo tudi s t[0], saj se obe imeni nanašata na en in isti objekt.

e.append(1)
Izpišimo t, da bomo videli, da je res. >>> t [[1]] In obratno, kar počnemo s t[0], se zgodi tudi z e.
t[0].append(2)
Zdaj izpišimo e, da vidimo, da se je res spremenil. >>> e [1, 2] Za konec, tako kot v prejšnjem primeru, spremenimo e.
e = []
Čemu prečrtana beseda? Ker je dvoumna. Ime e se je nanašalo na določen objekt (seznam, ki vsebuje enko). Ta seznam smo pustili pri miru, torej e-ja nismo spreminjali. Pač pa smo ustvarili nek nov objekt in ga priredili imenu e. Ker je ostal objekt, na katerega se je prej nanašalo ime, nedotaknjen, se tudi t ni spremenil: t še vedno vsebuje isti objekt in ta objekt je še vedno seznam, ki vsebuje enico, kot kaže slika in potrjuje spodnji poskus: >>> t [1]

Pač pa se je e spremenil v tem smislu, da se ime e ne nanaša več na isti objekt kot prej.

Primer 3

Sezname lahko, vemo, množimo s števili. Kar dobimo, je seznam, ki vsebuje večkrat ponovljen prvi seznam.

e = [] t = [e] * 3
Ko smo nekje na začetku rekli, da Python ne kopira objektov, smo mislili resno: t ne vsebuje treh praznih seznamov, temveč trikrat vsebuje isti prazen seznam, znan tudi pod imenom e. >>> t[0] is e True >>> t[1] is e True >>> t[2] is e True

Če je kdo predpostavil, da bodo to trije ne-isti seznami (torej enaki, saj so vsi prazni, vendar ne tudi isti), je zmotno mislil, da bo t = [e] * 3 naredil tri kopije eja. Ne, v t da trikrat isti e.

Ker gre za trikrat (štirikrat, če štejemo še e) isti seznam, se z enim spreminjajo vsi trije.

e.append(1)

Isto (da, enako bi bila tu prešibka beseda) bi se zgodilo tudi, če bi namesto k seznamu e dodali enico k seznamu t[0], t[1] ali t[2].

Kakšen je zdaj t, vemo. Le prepričajmo se:

>>> t [[1], [1], [1]]

In zdaj vas vprašam: se je t spremenil ali ne? Tisti, ki na to vprašanje odgovorijo z "da" ali z "ne", ne razumejo. Pravilen odgovor je, da vprašanje ni povsem jasno. V bistvu se t ni spremenil, saj ga tudi ni nihče spreminjal: t še vedno vsebuje natančno isto reč kot prej, trikrat en in isti objekt, seznam, ki ga poznamo tudi pod imenom e. Res pa se je spremenil ta seznam. Torej je t ostal enak, spremenilo se je le tisto, kar t vsebuje.

To je tako, kot da bi imel v roki pladenj. Če na ta paldenj nekaj dam ali z njega kaj vzamem, imam v roki še vedno isti pladenj, le različne reči so na njem. Ali po tem držim iste stvari ali ne, je stvar besed.

Primer 4

Naredimo nekaj podobnega kot prej: prazen seznam in nov seznam, v katerem bo trikrat ta, prazni seznam. V ta slednji seznam dodajmo še en prazen seznam.

e = [] t = [e]*3 t.append([])
Bistvo vaje je v tem, da t sicer vsebuje štiri prazne sezname, vendar so prvi trije isti, zadnji pa le enak. >>> t [[], [], [], []] >>> t[0] is e True >>> t[1] is e True >>> t[2] is e True >>> t[3] is e False

Če spremenimo e (se pravi, če vanj dodamo enico), se spremenijo prvi trije elementi tja, saj gre za isti seznam, četrti (t[3]) pa ostane, kakršen je bil, namreč prazen.

e.append(1)
>>> t [[1], [1], [1], []]

Primer 5

More seznam vsebovati sam sebe? Tega ni težko preskusiti.

t = [1, 2, 3] t.append(t)

Koliko elementov ima zdaj seznam t? Neskončno? Ne, nikakor, noben seznam ne more imeti neskončno elementov, to bi ne šlo v pomnilnik. Samo štiri ima, namreč 1, 2, 3 in še tisti seznam, ki ga poznamo tudi pod imenom t.

>>> len(t) 4 Element z indeksom tri je seveda t sam: >>> t[3] is t True Pa ga lahko izpišemo? Do neke mere. ;) >>> t [1, 2, 3, [...]] Python je zvit. Tretjega elementa ne izpisuje, saj ve, kaj bi se zgodilo potem. Pa ga lahko, ta, tretji element, izpišemo sami? >>> t[3] [1, 2, 3, [...]] Jasno, tretji element je tako ali tako t sam: če izpišemo t ali t[3], je to eno in isto.

Pa dodajmo v t še en element.

t.append(5)
Zdaj ima t še en element več, prav tako ima t[3] en element več, saj sta t in t[3] še vedno ena in ista reč. >>> t [1, 2, 3, [...], 5] >>> len(t) 5 >>> len(t[3]) 5

Primer 6

Definirajmo funkcijo, ki sprejme dva argumenta in ju malo spremeni.

def f(a, b): a = 2 b.append(3)

Vzemimo zdaj eno številko in en seznam.

x = 1 y = []

Pokličimo funkcijo, kot argumente ji dajmo x in y.

f(x, y)
Zdaj vidimo, kaj so pravzaprav argumenti funkcije, a in b: ker smo v oklepaju v definiciji funkcije navedli dve imeni, bo funkcija pričakovala dva argumenta, dva objekta. Ob klicu se imenoma argumentov, a in b, priredita objekta, poslana kot argumenta. Zgodi se isto, kot če bi rekli a = x in b = y. Če bi namesto f(x, y) funkcijo poklicali z f(sin(x)*2, ", ".join(imena), bi se zgodilo isto, kot če bi na začetku funkcije rekli a = sin(x)*2 in b = ", ".join(imena).

Imeni a in b se nanašata na ista objekta kot imeni x in y. Dril od tod naprej nam je znan in nič nas ne sme več presenetiti. Funkcija najprej reče

a = 2
Smo s tem spreminjali a? Takšnemu govorjenju se bomo danes, smo rekli, izogibali. Objekta, ki smo ga poprej imenovali a (in ga imenujemo tudi x), nismo spremenili, ostal je tak, kot je bil (in x z njim). Pač pa smo naredili objekt 2 in ga priredili imenu a.

Sledi spreminjanje bja.

b.append(3)
Tule v resnici spreminjamo b, točneje, objekt, ki ga imenujemo b. Ker ima isti objekt tudi ime y, se spreminja tudi objekt, ki ga imenujemo y. Če tako po vrnitvi iz funkcije izpišemo x in y, izvemo >>> x 1 >>> y [3]

Posebnost: inkrementalni operatorji

Operatorji, kot so +=, -= in *= zahtevajo posebnost. Nekateri objekti so, kot vemo, nespremenljivi - takšne so terke, pa tudi nizi in števila. Če rečemo a += 1, se ne spremeni objekt, ki smo ga prej imenovali a, temveč se k njemu prišteje 1 in rezultat (na novo) priredi imenu a. Z drugimi besedami, a += 1 je isto kot a = a + 1.

Če je objekt slučajno spremenljiv - edini, ki ga poznamo in podpira +=, je seznam - pa += ne sestavlja novega objekta, temveč spreminja obstoječega.

>>> a = 0 >>> b = a >>> a += 1 >>> b 0 >>> a = [] >>> b = a >>> a += [42] >>> b [42]

Operator += enkrat spreminja objekt, drugič naredi novega.

Ta izjema je neugledna. Sramotna. Ampak praktična. Pravilo, po katerem se ravna, je preprosto, operator += pa tako ali tako podpira malo tipov, torej se bomo hitro navadili, kako se obnaša pri katerem.

Zadnja sprememba: torek, 10. marec 2026, 18.11