Še o objektnem programiranju

Objektno programiranje je resna tema, ki je ni mogoče kar tako odpredavati v dveh tednih in se ga ni mogoče kar tako naučiti v dveh mesecih. Ker je vaš študij kratek in boste te reči kmalu potrebovali pri drugih predmetih, vam moramo tule predstaviti osnove. Ker vam mora priti stvar v kri, pa ne pričakujmo čudežev. Pri tem predmetu se boste naučili zgolj nekaterih tehnikalij, ne pa vsega, kar v zvezi s tem ponuja Python, vsega, kar ponujajo drugi jeziki in, predvsem, filozofije, ki je za tem.

Dedovanje

Definirajmo razred Oseba. Vsaka oseba ima ime in spol (Z ali M); oba podatka podamo kot argumenta konstruktorju. Oseba zna pozdravljati: metoda pozdravi izpiše Pozdravljeni, jaz sem {}, kjer namesto {} vstavi svoje ime.

class Oseba:
    def __init__(self, ime, spol):
        self.ime = ime
        self.spol = spol

    def pozdravi(self):
        print("Pozdravljeni, jaz sem {}".format(self.ime))

Tu smo mimogrede prvič srečali konstruktor (metodo __init__) z argumenti. Želva je imela konstruktor brez argumentov; novo želvo smo naredili s Turtle() in vedno je stala na sredini, obrnjena navzgor, vidna in s spuščenim peresom. Pri osebah ne bomo imeli "začetnega imena" (Janez Novak?!), temveč bomo morali ob konstrukciji objekta Oseba vedno povedati tudi njeno ime in spol.

benjamin = Oseba("Benjamin", "M")

Vse, kar naredi konstruktor, je, da prepiše vrednost argumenta v (istoimenski) atribut objekta. Se pravi, shrani argument v objekt.

Osebe znajo tudi vljudno pozdravljati:

benjamin.pozdravi()
Pozdravljeni, jaz sem Benjamin

Na fakulteti sta, poenostavljeno povedano, dve vrsti oseb, študenti in učitelji. Za študente beležimo njihove ocene pri posameznih predmetih. Ocene bomo pisali v slovar, katerega ključi bodo imena predmetov, vrednosti pa ocene pri teh predmetih. Študenta je mogoče oceniti, zato bo imel metodo oceni, ki ji bomo kot argument podali ime predmeta in oceno, pa bo zapisala to v slovar. Poleg tega bo imel funkcijo poprecje, ki bo izračunala njegovo poprečno oceno.

Poleg tega pa imajo tudi študenti ime in spol, in tudi pozdravljati znajo. Z drugimi besedami: študent je vrsta osebe. Študent ima vse lastnosti osebe in zna vse, kar znajo osebe, poleg tega pa še nekaj več (beležiti ocene in računati poprečja). Zato bomo razred Student izpeljali iz razreda Oseba (Angleži bi rekli Student is derived from Oseba). Student bo podedoval vse, kar ima Oseba (Student inherits attributes and methods from Oseba). Razred Oseba bo prednik (parent) razreda Student.

Kako to storimo? Zelo preprosto: ko definiramo razred Student, v oklepaju dodamo še razred, iz katerega naj bo ta izpeljan.

class Student(Oseba):
    def __init__(self, ime, spol):
        super().__init__(ime, spol)
        self.ocene = {}

    def oceni(self, predmet, ocena):
        self.ocene[predmet] = ocena

    def poprecje(self):
        if not self.ocene:
            return 0
        return sum(self.ocene.values()) / len(self.ocene)

Najprej preverimo, ali reč deluje.

rebeka = Student("Rebeka", "Z")
rebeka.oceni("Programiranje 1", 10)
rebeka.oceni("Uvod v racunalnistvo", 9)
rebeka.poprecje()
9.5

Sestavimo tri študente, zložimo jih v seznam in vsakemu študentu dodelimo deset naključnih ocen pri naključno izbranih predmetih.

from random import choice, randint

s1 = Student("Ana Karenina", "Z")
s2 = Student("Berta Novak", "Z")
s3 = Student("Cilka Celarec", "Z")

studenti = [s1, s2, s3]

predmeti = ["Uvod v racunalnistvo", "Programiranje 1", "Racunalniska arhitektura",
            "Matematika", "Diskretne strukture", "Programiranje 2",
            "Podatkovne baze", "Racunalniske komunikacije", "Operacijski sistemi",
            "Osnove verjetnosti in statistike"]

for student in studenti:
    for i in range(10):
        student.oceni(choice(predmeti), randint(6, 10))

Zdaj lahko izpišemo poprečne ocene vseh študentov.

for student in studenti:
    print(student.ime, student.poprecje())
Ana Karenina 8.142857142857142
Berta Novak 8.0
Cilka Celarec 7.571428571428571

Vse to so bile študentske zadeve. Ker je Rebeka tudi oseba, pa zna tudi pozdravljati.

rebeka.pozdravi()
Pozdravljeni, jaz sem Rebeka

Še enkrat preverite in se prepričajte: metode pozdravi nismo definirali v razredu Student, Rebeka pa vseeno pozdravlja. Ta metoda je torej podedovana od razreda-prednika Oseba.

Zdaj pa poglejmo definicijo metod razreda. Dodeljevanje ocene (oceni) je trivialno. Pri računanju poprečja (poprecje) moramo paziti le na študente, ki nimajo še nobene ocene, zato bo vseh tam enak 0.

Zanimiv pa je konstruktor, ki mora le pripraviti slovar, v katerega bo metoda oceni dodajala ocene. Ostalo pa prepusti podedovanemu konstruktorju - tako da ga pokliče.

Razred Student je podedoval, recimo, metodo pozdravi. Ko rečemo rebeka.pozdravi(), se pokliče podedovano pozdravljanje. Student je sestavil novo metodo poprecje in tudi tu ni dilem: ko rečemo rebeka.poprecje(), se pokliče funkcija oceni iz razreda Student. Metodi __init__ pa sta dve, podedovana in nova! Pravilo je preprosto: istoimenska metoda iz izpeljanega razreda povozi podedovano metodo.

Da je res tako, se najprej prepričajmo na preprostem zgledu: razredu Student definirajmo novo metodo pozdravi.

class Student(Oseba):
    def __init__(self, ime, spol):
        super().__init__(ime, spol)
        self.ocene = {}

    def oceni(self, predmet, ocena):
        self.ocene[predmet] = ocena

    def poprecje(self):
        if not self.ocene:
            return 0
        return sum(self.ocene.values()) / len(self.ocene)
        
    def pozdravi(self):
        print("Pozdravljeni, jaz sem {} in sem student(ka)".format(self.ime))

Sestavimo novo Rebeko in jo pozovimo, naj nas pozdravi.

rebeka = Student("Rebeka", "Z")
rebeka.pozdravi()
Pozdravljeni, jaz sem Rebeka in sem student(ka)

Rebeka ima tule dve metodi pozdravi, vendar se pokliče nova in ne podedovana.

Kaj pa, če bi hoteli poklicati podedovano metodo in ne nove? Tega ne počnemo. To je nenaravno, ne predstavljam si situacije, kjer bi nam v lepo napisanem programu to moglo priti prav ... razen v enem primeru: ko nova metoda kliče staro. Tule lahko razmišljamo takole: Student je izpeljan iz Oseba in torej zna pozdravljati. Vse, kar doda k pozdravu, je "in sem student(ka)". To pomeni, da bi lahko najprej prepustili pozdravljanje podedovani metodi in v Studentovem pozdravi le še dodatno izpisali "in sem student(ka)". To se stori takole:

class Student(Oseba):
    def __init__(self, ime, spol):
        super().__init__(ime, spol)
        self.ocene = {}

    def oceni(self, predmet, ocena):
        self.ocene[predmet] = ocena

    def poprecje(self):
        if not self.ocene:
            return 0
        return sum(self.ocene.values()) / len(self.ocene)
        
    def pozdravi(self):
        super().pozdravi()
        print("in sem student(ka)")

Rezultat sicer ni povsem enak ("in sem student(ka)" je namreč napisano v novi vrsti), a za primer bomo to vzeli v zakup.

super() je čudna funkcija, ki nam - precej poenostavljeno - vrne self kot da bi bil objekt razreda, ki je prednik razreda Student (torej razreda Oseba). Torej, če rečemo self.pozdravi() se pokliče metoda pozdravi, ki smo jo definirali v razredu Student, saj je self objekt razreda Student. Če pa pokličemo super().pozdravi(), se pokliče metoda pozdravi razreda Oseba, ker je razred Oseba prednik razreda Student. (Za tiste, ki veste, kaj je večkratno dedovanje: funkcija super v resnici vrača nekaj veliko veliko bolj zapletenega, saj je potrebno, kadar dedujemo iz večih razredov, vsakič poiskati, kateri od podedovanih razredov vsebuje funkcijo, ki jo želimo klicati. Če hočete moder nasvet: večkratno dedovanje je gladko tlakovana široka pot v pekel. Izogibajte se ga.)

Zdaj lahko končno pogledamo, kaj naredi konstruktor. Poleg self dobi še dva argumenta, ime, spol. Zanju poskrbi stari, podedovani konstruktor, zato ga pokličemo, takole: super().__init__(ime, spol). V novem konstruktorju je potrebno le še pripraviti slovar z ocenami.

Pa definirajmo še razred Ucitelj. Tudi ta je Oseba (ima ime in starost in zna pozdravljati). Ena od njegovih lastnosti je seznam predmetov, ki jih predava.

class Ucitelj(Oseba):
    def __init__(self, ime, spol, predmeti):
        super().__init__(ime, spol)
        self.predmeti = predmeti
        
u1 = Ucitelj("Tomaž Poljanšek", "M", ["Programiranje 2"])

Konstruktor nas, upam, ne preseneča več: ime in spol prepusti podedovanemu konstruktorju, sam pa le še nastavi seznam predmetov.

Kar je, je; česar ni, pa ni

Sestavimo funkcijo, ki izpiše imena vseh oseb na podanem seznamu.

def izpisi_imena(s):
    for e in s:
        print(e.ime)

Pokličemo jo lahko s seznamom študentov.

izpisi_imena(studenti)
Ana Karenina
Berta Novak
Cilka Celarec

Pokličemo jo lahko tudi s seznamom, v katerem so tako učitelji kot študenti.

ljudje = [s1, s2, s3, u1]
izpisi_imena(ljudje)
Ana Karenina
Berta Novak
Cilka Celarec
Tomaž Poljanšek

Pa če imamo kar nek seznam?

s = [1, 2, 3]
izpisi_imena(s)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-4fcdfb3963eb> in <module>
      1 s = [1, 2, 3]
----> 2 izpisi_imena(s)

<ipython-input-13-207846f9e46f> in izpisi_imena(s)
      1 def izpisi_imena(s):
      2     for e in s:
----> 3         print(e.ime)

AttributeError: 'int' object has no attribute 'ime'
s = ["Ana", "Berta", "Cilka"]
izpisi_imena(s)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-17-54c61afa8ccb> in <module>
      1 s = ["Ana", "Berta", "Cilka"]
----> 2 izpisi_imena(s)

<ipython-input-13-207846f9e46f> in izpisi_imena(s)
      1 def izpisi_imena(s):
      2     for e in s:
----> 3         print(e.ime)

AttributeError: 'str' object has no attribute 'ime'

Ne in ne. Ne s seznamom števil ne s seznamom nizov ni zadovoljna. Čemu ne? No, saj pove: 'int' object has no attribute 'ime'. Nekje v funkciji piše e.ime, pri čemer je e element seznama. V prvem primeru to pomeni, da hočemo poklicati 1.ime, kar je očitno traparija. V drugem hočemo klicati, recimo, "Ana".ime, kar prav tako ne gre. Ana sicer ni brez vsega, ima celo kup metod - replace, upper, endswith in še desetine drugih - a metode oz. atributa ime ni med njimi.

Funkcija izpisi_imena ima torej določene zahteve: kot argument ji moramo dati seznam (točneje: nekaj, prek česar je mogoče spustiti zanko for, recimo seznam) in elementi tega seznama morajo biti objekti, ki imajo polje ime. Z drugimi besedami: biti morajo objekti tipa Oseba.

Res? Pravzaprav ne. Zadošča, da imajo polje ime, ni pa treba, da so osebe.

class KrNeki:
    def __init__(self):
        self.ime = "jazbec"
a, b, c = KrNeki(), KrNeki(), KrNeki()

a
<__main__.KrNeki at 0x7f8bc08266d0>
a.ime
'jazbec'
izpisi_imena([a, b, c])
jazbec
jazbec
jazbec

(Tisti, ki so vajeni takoimenovanih statično tipiziranih jezikov, se zgražajo: funkcija izpisi_imena bi morala povedati, kakšnega tipa morajo biti objekti, ki jih sprejme. Tisti, ki imamo radi tudi dinamično tipizirane jezike, pa razumemo, zakaj je včasih boljše eno, včasih drugo.)

Podobno kot Python se obnašajo tudi drugi jeziki iste sorte, na primer JavaScript (kjer so reči še bolj drastične, saj imamo objektov, nimamo pa razredov!). Temu slogu (ne)preverjanja tipov pravimo duck typing: When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. Pri tem ni pomembno, ali gre v resnici za raco ali ne; če se objekt vede kot raca, ga lahko obravnavamo kot raco.

S slednjim je povezana še ena reč, ki je v dinamičnih jezikih precej drugačna kot v statično tipiziranih. (Tisti, ki nimate pojma, o čem govorim, se boste naučili, kako je to v Pythonu in se v drugem semestu čudili Javi. Tisti, ki poznate Javo ali C++ ali C#, pa se boste zdajle čudili Pythonu.)

Recimo, da bi imel metodo naziv, ki bi vrnila naziv osebe. Naziv osebe je lahko profesor, lahko pa je tudi student ali studentka. Če bi imeli takšno funkcijo, bi lahko pozdrav napisali takole.

    def pozdravi(self):
        print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))

Namreč, pred ime smo vrinili še self.naziv(), metodo, ki je (še?) nimamo in ki bo vrnila naziv.

No, pa naredimo, kot smo sklenili: spremenimo metodo pozdravi (obenem pa poenostavimo razred Student, odvzemimo mu ocene, zato pa tudi ne potrebuje več konstruktorja in ker v tem trenutku nima nobene metode, nekaj pa je znotraj razreda vseeno potrebno napisati, napišimo pass). Poglejmo, kaj se zgodi.

class Oseba:
    def __init__(self, ime, spol):
        self.ime = ime
        self.spol = spol
        
    def pozdravi(self):
        print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))
        
class Student(Oseba):
    pass
rebeka = Student("Rebeka", "Z")
rebeka.ime
'Rebeka'
rebeka.pozdravi()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-25-04d7d0f7c1c2> in <module>
----> 1 rebeka.pozdravi()

<ipython-input-22-80b095f344f1> in pozdravi(self)
      5 
      6     def pozdravi(self):
----> 7         print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))
      8 
      9 class Student(Oseba):

AttributeError: 'Student' object has no attribute 'naziv'

Rebeko smo uspeli narediti, tudi ime ima, vendar pa javi napako, ko ji rečemo, naj pozdravi: za to bi potrebovala metodo naziv(), te pa ni.

Metodo naziv pa bomo naredili ločeno za študente in za učitelje.

class Oseba:
    def __init__(self, ime, spol):
        self.ime = ime
        self.spol = spol
        
    def pozdravi(self):
        print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))
        
class Student(Oseba):
    def naziv(self):
        if self.spol == "Ž":
            return "študentka"
        else:
            return "študent"

class Ucitelj(Oseba):
    def naziv(self):
            if self.spol == "Z":
                return "profesorica"
            else:
                return "profesor"

Pa poglejmo, kako deluje.

s1 = Student("Ana Karenina", "Ž")

s1.pozdravi()
Pozdravljeni, jaz sem študentka Ana Karenina
u1 = Ucitelj("Tomaz Poljanšek", "M")

u1.pozdravi()
Pozdravljeni, jaz sem profesor Tomaz Poljanšek

Če pomislimo, kaj se tule v resnici dogaja, je stvar nekoliko fascinantna. Poklicali smo metodo pozdravi. Metoda pozdravi je podedovana iz Oseba - ne Student ne Ucitelj nimata svoje metode pozdravi. Izvaja se torej pozdravi, ki je definirana v Oseba. In med izvajanjem te metode pokličemo metodo self.naziv. V prvem primeru (Ana) je self objekt razreda Student. Razred Student ima metodo naziv in ko pozdravi pokliče self.naziv se torej pokliče Studentov naziv. V drugem primeru (Tomaz) je self objekt razreda Ucitelj, torej je self.naziv Uciteljev naziv. Torej: ko pozdravi pokliče self.naziv to včasih pomeni funkcijo naziv, ki je definirana v Student, včasih pa funkcijo naziv, ki je definirana v Ucitelj.

Tako kot je funkcija izpisi_imena zahtevala, da imajo vsi objekti na seznamu ime, tako metoda pozdravi zahteva, da ima self metodo naziv (ki ne sprejme drugega argumenta kot self in ki vrača nekaj, kar se da izpisati...). Odkod se ta naziv vzame - je podedovan, je na novo definiran v selfovem razredu ali pa je prišel v self po kakih drugih, sumljivejših poteh - ji je vseeno. Pomembno je le, da self.naziv obstaja.

Abstraktni razredi

Kar smo povedali ravnokar, je res res pomembno. To je najpomembnejša reč v objektnem programiranju, zato jo moramo še malo raziskovati.

nekdo = Oseba("Akakij Akakijevič", "M")

nekdo.pozdravi()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-a47a23fbd476> in <module>
      1 nekdo = Oseba("Akakij Akakijevič", "M")
      2 
----> 3 nekdo.pozdravi()

<ipython-input-26-f11e22da7a6f> in pozdravi(self)
      5 
      6     def pozdravi(self):
----> 7         print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))
      8 
      9 class Student(Oseba):

AttributeError: 'Oseba' object has no attribute 'naziv'

To smo v bistvu že videli zgoraj: študenti niso znali pozdravljati, dokler jim nismo priskrbeli metode naziv. A to v bistvu pomeni, da je napaka v razredu Oseba, ker zahteva nekaj, česar (potencialno) ni.

Temu se ne reče napaka. Temu se reče abstrakten razred. V nekaterih jezikih bi bilo potrebno "napovedati" metodo naziv kot "čisto navidezno metodo" (pure virtual method). Razredu Oseba bi rekli, da je abstrakten (abstract class) in objektov tega razreda ne bi bilo mogoče sestavljati - napako bi dobili že ob klicu Oseba("Akakij Akakijevič", "M"), češ da takšnih, nepopolnih objektov ne sme biti.

V Pythonu in podobnih jezikih ni tako. Razred Oseba je okrnjen, vendar bi lahko načelno imel tudi kakšne metode, ki bi normalo delovale. Študenti bi lahko še vedno prejemali ocene, le pozdravljali ne bi.

Nadaljujmo: dodajmo še en razred. Ali dva.

class Snazilka(Oseba):
    def __init__(self, ime):
        super().__init__(ime, "Ž")
        
class Hisnik(Oseba):
    def __init__(self, ime):
        super().__init__(ime, "M")
        
fata = Snazilka("Fata")

fata.pozdravi()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-30-6c9c721ce1f8> in <module>
      9 fata = Snazilka("Fata")
     10 
---> 11 fata.pozdravi()

<ipython-input-26-f11e22da7a6f> in pozdravi(self)
      5 
      6     def pozdravi(self):
----> 7         print("Pozdravljeni, jaz sem {} {}".format(self.naziv(), self.ime))
      8 
      9 class Student(Oseba):

AttributeError: 'Snazilka' object has no attribute 'naziv'

Da Snazilka in Hisnik ne znata pozdravljati, je jasno in pričakovano. Ne, ker ne bi znala, le ne vesta, kako bi se predstavila. Podobno bi se zgodilo, ko bi dodali tajnico in osebje študentskega referata.

V vsakem jeziku in družbi pa se razvijejo norme naslavljanja neznancev. V davnih časih moje mladosti se je neznancem uradno reklo tovariš in tovarišica (v praksi se je to nanašalo samo na učitelje in učiteljice), danes sta to gospod in gospa. To so privzeti nazivi, odvisno od situacije pa uporabimo drugačnega (doktor, če gre za zdravnika ali koga, ki se je potrudil napisati daljše besedilo, ki je zaradi vezave v črno usnje dajalo vtis pomembnosti; docent, izredni profesor ali profesor za nekoga, ki je naredil vse potrebne kljukice in tako naprej). Vsi, ostali, ki niso nič posebnega, pa so torej gospodje in gospe, zato opremimo Osebo s tem, privzetim nazivom.

class Oseba:
    def __init__(self, ime, spol):
        self.ime = ime
        self.spol = spol
        
    def pozdravi(self):
        print("Pozdravljeni, jaz sem {} {}.".format(self.naziv(), self.ime))
        
    def naziv(self):
        if self.spol == "Ž":
            return "gospa"
        else:
            return "gospod"
        
class Student(Oseba):
    def naziv(self):
        if self.spol == "Ž":
            return "študentka"
        else:
            return "študent"

class Ucitelj(Oseba):
    def naziv(self):
            if self.spol == "Z":
                return "profesorica"
            else:
                return "profesor"

class Snazilka(Oseba):
    def __init__(self, ime):
        super().__init__(ime, "Ž")
        
class Hisnik(Oseba):
    def __init__(self, ime):
        super().__init__(ime, "M")

Poselimo fakulteto.

ana = Student("Ana Karenina", "Ž")
tomaz = Ucitelj("Tomaž Poljanšek", "M")
fata = Snazilka("Fata")
joze = Hisnik("Jože")

Vsi so vljudni in se lepo pozdravljajo.

ana.pozdravi()
Pozdravljeni, jaz sem študentka Ana Karenina.
tomaz.pozdravi()
Pozdravljeni, jaz sem profesor Tomaž Poljanšek.
fata.pozdravi()
Pozdravljeni, jaz sem gospa Fata.
joze.pozdravi()
Pozdravljeni, jaz sem gospod Jože.

ana.pozdravi() pokliče Osebin pozdravi (temu bomo poslej rekli Oseba.pozdravi; to v resnici obstaja, Pythonu lahko zares napišemo Oseba.pozdravi, a to, kar v tem primeru dobimo, je nekoliko težko razložiti). Torej ana.pozdravi() pokliče metodo Oseba.pozdravi. Le-ta kliče self.naziv. Ker je self isto kot ana, ana pa je objekt tipa (ali, kot sem govoril prej, razreda - stvar navade) Student, self.naziv pomeni ana.naziv, torej Student.naziv. Rezultat je niz "studentka".

dani.pozdravi() pokliče Oseba.pozdravi. Le-ta spet kliče self.naziv. Ker je self isto kot dani, dani pa je objekt tipa Ucitelj, self.naziv pomeni Ucitelj.naziv. Rezultat je niz "profesor", saj dani.naziv() vrne "profesor".

And now for something completely different. fata.pozdravi() pokliče Oseba.pozdravi. Le-ta spet kliče self.naziv. Ker je self isto kot fata, fata pa je objekt tipa Snazilka, self.naziv pomeni Snazilka.naziv. Tega pa ni! Pač pa ima fata naziv, ki ga je podedovala od Oseba, namreč Oseba.naziv. In ta pravi, da je Fata gospa.

O Nepythonu

Če želimo na tem predavanju vsaj omeniti tudi objektno programiranje na splošno, ne le v Pythonu, moramo povedati za bistveno razliko med objektnim programiranjem v Pythonu (in drugih tipičnih dinamično tipiziranih jezikih (se opravičujem za besednjak), na primer JavaScriptu) in objektnim programiranjem v statično tipiziranih jezikih, na primer C++, Javi in C#.

Metodam, kakršen je tale naziv, pravimo navidezne metode (virtual method). Za navidezne metode je značilno, da imajo različni podedovani razredi lahko različne metode, pri čemer pa predniki "vedo", katero metodo poklicati. Tako Oseba.pozdrav včasih pokliče Oseba.naziv, včasih Student.naziv in včasih Ucitelj.naziv. Če bi se pojavil še kak četrti, peti, ali osmi razred s svojim nazivom, bi znal Oseba.pozdrav poklicati tudi tega.

V Pythonu izraz sam nima pomena (čemu "navidezna"?!), pa tudi govoriti o navideznih metodah v Pythonu nima veliko smisla, saj drugačnih metod kot navideznih v Pythonu sploh ni. V večini statično tipiziranih jezikih pa obstajajo tudi "nenavidezne" metode. Če naziv ne bi bil navidezna metoda, bi Oseba.pozdrav vedno poklical Oseba.naziv, četudi bi imel objekt, s katerim imamo opravka, svoj, "specializiran" naziv.

Drugo, po čemer se ti jeziki razlikujejo od Pythona (in Javascripta in podobnih), je, da bi, kot smo že omenili, zahtevali, da je Oseba.naziv nujno definiran (točneje: deklariran), če hočemo, da Oseba.pozdrav kliče self.naziv - ne glede na to, ali bi bila to navidezna metoda ali ne. Včasih se zgodi tudi, da funkcije, kakršna je Oseba.naziv, sploh ne moremo napisati (ker bi morala delati kaj, česar se z Osebo ne da narediti, temveč lahko to delajo šele njeni nasledniki). V tem primeru, uh, bi definirali čisto navidezno metodo (pure virtual method), razred Oseba bi bila abstraktni razred ... No, boljše, da ne rinemo v to. Vedite le, da nam je v Pythonu prihranjenih veliko komplikacij.

Na predavanjih pri Programiranju 1 objektno programiranje v Pythonu popraskamo le po površju. Veliko konceptov, kot na primer statične metode, spremenljivke razreda in podobno, ki ste jih morda srečali v drugih jezikih, pozna tudi Python. Vendar je vse to še pretežko za večino poslušalcev tega predmeta. Te stvari boste spoznavali sproti. Še več pa je tehnik, ki so specifične za Python ali pa jih poznajo le skriptni jeziki. Tudi za te pri Programiranju 1 žal ne bo časa.