2.22. Periytyminen¶
Periytyminen antaa luokan laajentaa toista luokkaa – aloittaa kaikilla sen metodeilla ja attribuuteilla, sitten lisätä tai korvata sen, mikä on erilaista. Alkuperäistä kutsutaan kantaluokaksi (tai vanhemmaksi); laajennusta kutsutaan aliluokaksi.
Square käyttää uudelleen jokaista Shape-luokan metodia ja korvaa ne, jotka sen täytyy erikoistaa.¶
2.22.1. Aliluokan määrittely¶
Kantaluokka menee class-rivin sulkeisiin:
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())
Tuloste:
square with area 16
Square perii describe-metodin Shape-luokalta muuttumattomana ja korvaa area-metodin palauttamaan todellisen arvon. Kantaluokan describe toimii edelleen, koska se kutsuu self.area()-metodia – joka ratkeaa korvaukseen suoritusaikana.
2.22.2. super()¶
super() palauttaa välityskohteen, jonka avulla metodi voi kutsua metodin kantaluokkaversiota. Yleisin käyttötapa on kantaluokan __init__-metodin kutsuminen aliluokan __init__-metodista, jotta kantaluokka pääsee alustamaan omat attribuuttinsa:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
super().__init__-kutsun unohtaminen on yleinen bugien lähde: attribuutteja, jotka kantaluokka olisi asettanut, ei koskaan aseteta, ja niihin nojaavat perityt metodit kaatuvat myöhemmin.
2.22.2.1. Implisiittinen kantaluokka¶
Jokainen luokka periytyy implisiittisesti object-luokasta, joka on Pythonin tyyppihierarkian juuri. Metodit kuten __repr__, __eq__ ja attribuuttien käytön takana oleva koneisto tulevat sieltä kaikki, vaikka luokka ei ilmoittaisi yhtään kantaluokkaa. class Foo(object):-kirjoitustapa ja class Foo:-kirjoitustapa ovat nykyaikaisessa Pythonissa vastaavia; jälkimmäinen on sopimuksenmukainen muoto.
2.22.3. Milloin periytyminen auttaa – ja milloin ei¶
Käytä periytymistä, kun yksi luokka on aidosti tietyn tarkempi laji toista, jakaen suurimman osan käyttäytymisestä. Klassinen testi on ”on-a”-tarkistus: Square on Shape.
Kun kaksi luokkaa vain sattuvat jakamaan muutaman apurin, periytyminen on liioittelua. Tartu sen sijaan koostamiseen: anna yhden luokan pitää toisen instanssia attribuuttina ja käytä sitä tuon attribuutin kautta. Muoto on ”on-jotain”, ei ”on-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")
Molemmat toimivat. Koostava versio on yleensä parempi, koska se:
Pitää rajapinnat pieninä.
WorkerComposespaljastaa vainrun- jalogger-jäsenet.WorkerInheritspaljastaa myöslog-metodin – kutsujat voivat kirjoittaaworker.log(...)suoraan, oli se tarkoitettu tai ei.Irrottaa elinkaaret toisistaan. Loggeri voidaan vaihtaa, jakaa työntekijöiden kesken tai rakentaa eri tavalla työntekijää kohden, kaikki koskematta
Worker-luokkaan. Aliluokka on pultattu kiinni kantaluokkaansa.Välttää metodinimien törmäykset. Kaksi kantaluokkaa, jotka molemmat määrittelevät
run-metodin, törmäävät, kun luokka yrittää periytyä molemmista; kaksi attribuuttia ei koskaan törmää.
Käytännön nyrkkisääntö:
Käytä periytymistä, kun aliluokka todella on kantaluokan laji ja jokainen kantaluokan metodi on järkevä myös siinä –
SquareonShape;MemoryErroronException.Käytä koostamista kaikkeen muuhun – kun haluat vain toisen luokan käyttäytymisen, et sen identiteettiä. Useimmat todelliset koodit käyttävät koostamista paljon enemmän kuin periytymistä.