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.

Kantaluokka Shape osoittaa alaspäin aliluokkaan Square; Square korvaa yhden metodin (area) ja perii loput.

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ä. WorkerComposes paljastaa vain run- ja logger-jäsenet. WorkerInherits paljastaa myös log-metodin – kutsujat voivat kirjoittaa worker.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ä – Square on Shape; MemoryError on Exception.

  • 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ä.