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.janez
pripada objektux
. Tja smo ga postavili, ko smo reklix.janez = "novak"
.Ko napišemo
x.g
Python pogleda vx
. Vidi, da je tam lejanez
. Vendar ne obupa. Ker jex
objekt razredaB
, pogleda, ali se v imenskem prostoruB
ja nemara nahaja kakg
. Se.x.g
je 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.f
Python pogleda vx
. Tam je lejanez
. Nato pogleda vB
, vendarf
ja tudi tam ni. Zdaj pogleda, iz katerega razreda je izpeljanB
. Vidi, da izA
, torej gre gledat tja. Tam res najdef
.>>> x.f <bound method A.f of <__main__.B object at 0x105dc02e8>>
V izpisu lahko vidimo, da gre za
A
jevf
objektaB
. Da, natančno o tem bomo še kaj rekli.Ko napišemo
x.__str__
- metodo, ki, vemo, skrbi za izpisx
a, Python pogleda vx
(kjer je ni), vB
(kjer je ni) in vA
(kjer je ni). Nato pogleda, iz katerega razreda je izpeljanA
. Navidez iz nobenega, v resnici pa so vsi razredi, ki niso izpeljani iz nobenega drugega, izpeljani iz razredaobject
. 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 metodaobject
a.
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 G
jevih
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
A
jeva 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 C
ja.
Č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 self
om.
>>> 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
c
jef 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
.