2.22. Nasljeđivanje¶
Nasljeđivanje omogućuje klasi da proširi drugu klasu – da počne sa svim njezinim metodama i atributima, a zatim doda ili nadjača ono što je drukčije. Original se naziva baza (ili roditelj); proširenje se naziva podklasa.
Square ponovno koristi svaku metodu na Shape i nadjačava one koje treba specijalizirati.¶
2.22.1. Definiranje podklase¶
Bazna klasa ide u zagrade na retku class:
class Shape:
def __init__(self, name):
self.name = name
def describe(self):
return self.name + " with area " + str(self.area())
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side
def area(self):
return self.side * self.side
s = Square(4)
print(s.describe())
Izlaz:
square with area 16
Square nepromijenjeno nasljeđuje describe od Shape i nadjačava area da vrati stvarnu vrijednost. Bazna metoda describe i dalje radi jer poziva self.area() – što se u vrijeme izvođenja razrješava na nadjačanu metodu.
2.22.2. super()¶
super() vraća proxy koji omogućuje metodi da pozove baznu verziju metode. Najčešća upotreba je pozivanje baznog __init__ iz __init__ podklase kako bi baza inicijalizirala vlastite atribute:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Zaboravljanje super().__init__ čest je izvor grešaka: atributi koje bi baza postavila nikad se ne postave, a naslijeđene metode koje se na njih oslanjaju kasnije se sruše.
2.22.2.1. Implicitna bazna klasa¶
Svaka klasa implicitno nasljeđuje od object, korijena Pythonove hijerarhije tipova. Metode poput __repr__, __eq__ i mehanizam iza pristupa atributima sve potječu odande čak i kad klasa ne deklarira nijednu bazu. Eksplicitno pisanje class Foo(object): i pisanje class Foo: ekvivalentni su u modernom Pythonu; potonji je konvencionalni oblik.
2.22.3. Kada nasljeđivanje pomaže – a kada ne¶
Koristi nasljeđivanje kad je jedna klasa stvarno specifičnija vrsta druge, dijeleći većinu ponašanja. Klasični test je provjera „jest-a”: Square jest Shape.
Kad se dvije klase tek slučajno dijele nekoliko pomoćnika, nasljeđivanje je pretjerano. Posegni umjesto toga za kompozicijom: neka jedna klasa drži instancu druge kao atribut i koristi je kroz taj atribut. Oblik je „ima-a”, ne „jest-a”:
class Logger:
def log(self, msg):
print("[log]", msg)
# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
def run(self):
self.log("starting")
# composition: Worker HAS-A Logger
class WorkerComposes:
def __init__(self):
self.logger = Logger()
def run(self):
self.logger.log("starting")
Oba rade. Verzija s kompozicijom obično je bolja jer:
Drži sučelja malenima.
WorkerComposesizlaže samorunilogger.WorkerInheritsizlaže ilog– pozivatelji mogu izravno pisatiworker.log(...), bilo da je to bilo namjeravano ili ne.Razdvaja životne vijekove. Logger se može zamijeniti, dijeliti među radnicima ili konstruirati drukčije po radniku, sve to bez diranja klase
Worker. Podklasa je pričvršćena za svoju bazu.Izbjegava sudare imena metoda. Dvije baze koje obje definiraju
runsudaraju se kad klasa pokuša naslijediti od obje; dva atributa nikad se ne sudaraju.
Praktično pravilo:
Koristi nasljeđivanje kad podklasa stvarno jest vrsta baze i kad svaka metoda na bazi ima smisla i na njoj –
SquarejestShape;MemoryErrorjestException.Koristi kompoziciju za sve ostalo – kad želiš samo ponašanje druge klase, a ne njezin identitet. Većina stvarnog koda koristi kompoziciju daleko više nego nasljeđivanje.