Zapiski
Atributi objektov
Sestavimo si dva razreda. B je izpeljan iz A. A ima metodo f in B
ima metodo g.
class A:
    def f(self, x):
        return 2 * x
class B(A):
    def g(self, x):
        return x + 42
x = B()
y = B()
x.janez = "novak"
Zdaj imamo
>>> x.janez
'novak'
>>> x.g(42)
49
>>> x.f(42)
84
>>> b.__str__
<method-wrapper '__str__' of B object at 0x10d7d0c50>
Vsaka vrstica je drugačna - vsak atribut se potegne od drugod.
- x.janezpripada objektu- x. Tja smo ga postavili, ko smo rekli- x.janez = "novak".
- Ko napišemo - x.gPython pogleda v- x. Vidi, da je tam le- janez. Vendar ne obupa. Ker je- xobjekt razreda- B, pogleda, ali se v imenskem prostoru- Bja nemara nahaja kak- g. Se.- x.gje torej- >>> x.g <bound method B.g of <__main__.B object at 0x103e41320>>- O tem bomo še kaj rekli, vendar najprej končajmo tole. 
- Ko napišemo - x.fPython pogleda v- x. Tam je le- janez. Nato pogleda v- B, vendar- fja tudi tam ni. Zdaj pogleda, iz katerega razreda je izpeljan- B. Vidi, da iz- A, torej gre gledat tja. Tam res najde- f.- >>> x.f <bound method A.f of <__main__.B object at 0x105dc02e8>>- V izpisu lahko vidimo, da gre za - Ajev- fobjekta- B. Da, natančno o tem bomo še kaj rekli.
- Ko napišemo - x.__str__- metodo, ki, vemo, skrbi za izpis- xa, Python pogleda v- x(kjer je ni), v- B(kjer je ni) in v- A(kjer je ni). Nato pogleda, iz katerega razreda je izpeljan- A. Navidez iz nobenega, v resnici pa so vsi razredi, ki niso izpeljani iz nobenega drugega, izpeljani iz razreda- object. Izpis tu sicer ni povsem jasen:- >>> x.__str__ <method-wrapper '__str__' of B object at 0x10641e0b8>- V resnici se v ozadju dogaja še nekaj, o čemer bi se tule težko pogovarjali, a - __str__je v bistvu metoda- objecta.
Kar smo videli do sem, je snov Programiranja 1. Za tiste, ki jih moti, da
niso izvedeli nič novega, dodajmo le še, da nam je Python voljan povedati,
kje bo iskal metode objektov razreda B.
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
Najprej bo pogledal v B, nato A, nato object. Razred bool je
- spomnimo se - izpeljan iz int. Česar ni specifično definiral bool,
Python išče v int.
>>> bool.__mro__
(<class 'bool'>, <class 'int'>, <class 'object'>)
Če imamo bolj zapleteno hierarhijo razredov, je MRO še bolj zanimiv
class D:
    pass
class E:
    pass
class F(D, E):
    pass
class G(F, int):
    pass
Tule sta F in G izpeljana iz dveh razredov; vrstni red iskanja Gjevih
metod je
>>> G.__mro__
(<class '__main__.G'>, <class '__main__.F'>, <class '__main__.D'>,
<class '__main__.E'>, <class 'int'>, <class 'object'>)
Atributi razredov
V gornjih izpisih smo opazili nekaj zanimivega.
>>> x.g
<bound method B.g of <__main__.B object at 0x103e41320>>
Kaj je B.g? Tudi razredi so objekti. Torej imajo tudi razredi lahko
atribute. B, recimo, ima atribut g.
>>> B.g
<function B.g at 0x10d7ca488>
Kaj se dogaja tule, bomo lažje razumeli, če nekoliko dopolnimo definiciji razredov.
class A:
    baz = "Ana"
    def f(self, x):
        return 2 * x
class B(A):
    foo = 42
    bar = 13
    def g(self, x):
        return x + 42
Zdaj ima B tri stvari: "spremenljivki" foo in bar ter funkcijo g.
Resno.
>>> B.foo
42
>>> B.bar
13
>>> B.g
<function B.g at 0x10d7ca510>
A ima baz in f.
>>> A.baz
'Ana'
>>> A.f
<function A.f at 0x10d7ca598>
Vse, kar ima A, ima tudi B.
>>> B.baz
'Ana'
>>> B.f
<function A.f at 0x10d7ca598>
Zadnji izpis pove, da je B.f Ajeva funckija. Seveda, gre za eno in
isto reč. Kdor ne verjame, naj vpraša.
>>> B.f is A.f
True
Zdaj naredimo še objekt razreda B in se poigrajmo z njim.
>>> b = B()
>>> b.foo
42
b ima atribut foo; dobil ga je iz B. Nas to čudi? Ne bi nas smelo
(več). Razredi so očitno podobni modulom. Podobni so majhnim programom.
Vse, kar sestavimo v njih, je del razreda in tako vidno objektom tega razreda.
class B(A):
    import math
    foo = 42
    bar = 13
    def g(self, x):
        return x + 42
Nenavadno, vendar - zdaj ima razred B pač tudi modul math.
>>> B.math
<module 'math' from '/Users/janez/env/o3/lib/python3.5/lib-dynload/math.cpython-35m-darwin.so'>
Če ga ima razred, ga imajo tudi objekti tega razreda.
>>> b = B()
>>>
>>> b.math.sqrt(42)
6.48074069840786
Tega seveda ne počnemo - ne vem, zakaj bi. Pokazal sem samo, da bi videli mehanizem, kako reč deluje.
Metode torej niso nič drugega kot funkcije, ki so znotraj razredov. No ja,
z malo dodatne magije. Sestavimo razred s funkcijo f, ki množi podani
argument s faktorjem, ki ga določimo v konstruktorju.
class C:
    def __init__(self, faktor):
        self.faktor = faktor
    def f(self, x):
        return self.faktor * x
Preverimo.
>>> u = C(5)
>>> u.faktor
5
>>> u.f(12)
60
Kot mora biti. Objekt u ima atribut faktor, ker smo ga določili v
konstruktorju, in metodo f, ker jo je dobil od Cja.
Če lahko do funkcije f pridemo tudi prek C.f (to je ena in ista reč,
mar ni?) - jo lahko tudi pokličemo kar direktno?
>>> C.f(12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 'x'
Kaj gre narobe, bomo še bolj jasno videli, če pokličemo C.f brez argumentov.
>>> C.f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 2 required positional arguments: 'self' and 'x'
Python pove, da hoče f dva argumenta, namreč self in x. Kako?! Saj
vendar nikoli nismo podajali self kot argument. Tudi u.f(12) je delovalo
- pa smo dali le en argument, namreč 12. Odgovor je tule:
>>> C.f
<function C.f at 0x10d7ca7b8>
>>> u.f
<bound method C.f of <__main__.C object at 0x10d7d6be0>>
C.f je funkcija, ki zahteva dva argumenta, self in x. Ko do taiste
funkcije pridemo prek u.f, pa Python pripne prvi argument. Sestavi
novo funkcijo, v kateri je self že postavljen na u, zato ji manjka
le še en argument, namreč x. Klic u.f(12) je torej ekvivalenten klicu
C.f(u, 12). Resnično,
>>> C.f(u, 12)
60
Glejte tole:
>>> s = [1, 2, 3]
>>> s.append
<built-in method append of list object at 0x10d7d43c8>
>>> list.append
<method 'append' of 'list' objects>
Objekt s je tipa list. Zato je s.append v bistvu list.append, vendar
s pripetim selfom.
>>> s.append(4)
>>> list.append(s, 5)
>>> s
[1, 2, 3, 4, 5]
C.f ni vezan na katerikoli objekt (v starejših verzijah Pythona so ga
 imenovali unbound method), u.f pa je f vezan na u (zato
 bound method).
Kaj nam to koristi?
Kako deluje, je vedno koristno vedeti. Nikoli ne veš, kdaj ti pride prav. Poleg tega, je kako deluje lahko zabavno. Pokažimo, da je tudi uporabno.
Tule bo le nekaj primerov. Zares imenitno pa bo to postalo prihodnje leto, ko bomo delali zapletene reči, ki bodo, če bomo poznali te in podobne trike, zaradi tega nekoliko preprostejše.
Nevezane metode so lahko uporabne
Imamo seznam nizov.
>>> t = ["ABCDEF", "Def", "BeCD"]
Radi bi naredili seznam taistih nizov z malimi črkami. Uporabili bomo map
(čeprav bi lahko tudi izpeljane sezname).
>>> list(map(lambda x: x.lower(), t))
['abcdef', 'def', 'becd']
Je res potrebno pisati lambdo, lambda x: x.lower(). Ne, rečemo lahko kar
>>> list(map(str.lower, t))
['abcdef', 'def', 'becd']
Če je x nek niz, je x.lower metoda niza x, ki bo vrnila (prav ta in
le ta) x z malimi črkami. Poklicati jo moramo brez argumentov (edini
argument, self, je že vezan).
str.lower pa je nevezana metoda. Podali ji bomo niz in dobili taisti niz
z malimi črkami.
>>> str.lower("Janez Novak")
'janez novak'
Če premapiramo ves t čez str.lower, dobimo vse te nize z malimi črkami.
Kako bi uredili te nize?
>>> sorted(t)
['ABCDEF', 'BeCD', 'Def']
Sem rekel po abecedi? Ne, mislil sem po dolžini; se opravičujem. Vemo, vemo:
sorted sprejema argument key, ki mu lahko podamo funkcijo, po kateri naj
primerja nize. Podati je potrebno funkcijo, ki vrne dolžino niza,
lambda x: len(x).
>>> sorted(t, key=lambda x: len(x))
['Def', 'BeCD', 'ABCDEF']
Po tem, kar smo se naučili prejšnjič in danes, vemo, da je to nepotrebno
kompliciranje. Funkcija, ki vrača dolžino niza, je str.__len__. Torej
>>> sorted(t, key=str.__len__)
['Def', 'BeCD', 'ABCDEF']
Neuporabno? Kdo pa hoče urejati nize po dolžini? Kaj pa tole?
>>> t = ["aBCDEF", "Def", "BeCD"]
>>> sorted(t)
['BeCD', 'Def', 'aBCDEF']
Od kdaj je "a" po abecedi za "B" in "C"? Odkar so - za Python in bolj ali manj vse druge jezike tudi - male črke po abecedi za velikimi. Če hočemo, da Python ne dela razlik med malimi in velikimi črkami, mu recimo, naj nize primerja glede na to, kako so videti, če jih zapišemo z malimi črkami.
>>> sorted(t, key=str.lower)
['aBCDEF', 'BeCD', 'Def']
Hočemo prvi niz po abecedi - ne glede na velike in male črke?
>>> min(t, key=str.lower)
'aBCDEF'
Skupni atributi
Sestavimo takle razred.
class A:
    argumenti = []
    def f(self, x):
        self.argumenti.append(x)
        return 2 * x
In dva objekta.
>>> a = A()
>>> b = A()
>>> a.s = []
>>> b.s = []
Oba, a in b imata zdaj seznam s in seznam argumenti.
>>> a.s
[]
>>> a.argumenti
[]
Če v a.s dodamo 42, bo b.s ostal, kar je bil. Jasno. Tako mora biti.
>>> a.s.append(42)
>>> b.s
[]
Seznam argumenti pa ni v a ali b temveč v A. Vsak objekt ima
svoj s, vsi pa imajo skupen argumenti.
>>> a.s is b.s
False
>>> a.argumenti is b.argumenti
True
Aha:
>>> a.argumenti.append("Tine")
>>> b.argumenti
['Tine']
To izkorišča metoda f, ki shranjuje vse argumente, s katerimi je bila
kdajkoli poklicana.
>>> a.f(13)
26
>>> a.f("Ana")
'AnaAna'
>>> b.f(17)
34
In zdaj poglejmo spisek:
>>> A.argumenti
['Tine', 13, 'Ana', 17]
Pogledali smo ga prek razreda A. Seveda bi ga lahko tudi prek posameznega
objekta,
>>> a.argumenti
['Tine', 13, 'Ana', 17]
vendar je argumenti stvar razreda, pa ga zato tudi glejmo prek razreda.
__rmul__ = __mul__
To prirejanje smo uporabili, ko smo definirali razred Vector:
class Vector:
    # Najprej nekaj drugih metod, nato pa.
    def __mul__(self, other):
        if isinstance(other, Vector):
            return sum(x * y for x, y in zip(self.values, other.values))
        else:
            return Vector(*[x * other for x in self.values])
    __rmul__ = __mul__
Zdaj razumemo tudi tole, ne? Če je razred kot majhen program, lahko v njem prosto računamo.
class A:
    from math import sqrt
    a = 42
    b = sqrt(a + 7)
    t = sqrt
Če zdaj naredimo objekt tega (ne preveč uporabnega) razreda, ima vse, kar smo sestavili v tem razredu.
>>> a = A()
>>> a.a
42
>>> a.b
7.0
>>> a.t
<built-in function sqrt>
>>> a.t(25)
5.0
Tisti __rmul__ = __mul__ le naredi novo spremenljivko (ki jo zunaj
vidimo kot atribut) __rmul__ in ki pomeni isto kot __mul__.
Privzete vrednosti atributov
Sestavimo drugačen množilnik. Namesto, da bi nastavljal faktor v konstruktorju,
bo nastavljen kar kot atribut razreda C.
class C:
    faktor = 1
    def f(self, x):
        return self.faktor * x
Sestavimo dva objekta.
>>> c = C()
>>> d = C()
Obe imata atribut faktor - gre kar za C-jev faktor.
>>> c.faktor
1
>>> d.faktor
1
Metoda f, jasno, deluje.
>>> c.f(12)
12
>>> d.f(12)
12
Zdaj pa postavimo c.faktor.
>>> c.faktor = 5
>>> c.f(12)
60
>>> d.f(12)
12
Ko metoda f išče self.faktor, najprej pogleda v self, šele nato v C.
Kadar je self c, bo self.faktor cjef faktor. Ta je 5, zato dobimo
60. Ko je self enak d, pa Python ugotovi, da d nima faktorja, zato
ga išče (in najde) v razredu C.