2.22. Öröklődés¶
Az öröklődés lehetővé teszi, hogy egy osztály kiterjesszen egy másik osztályt – annak összes metódusából és attribútumából kiindulva, majd hozzáadva vagy felülírva azt, ami eltér. Az eredetit bázisnak (vagy szülőnek) nevezik; a kiterjesztést pedig alosztálynak.
A Square a Shape minden metódusát újrahasználja, és csak azokat írja felül, amelyeket specializálnia kell.¶
2.22.1. Alosztály definiálása¶
A bázisosztály a class sor zárójeleibe kerül:
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())
Kimenet:
square with area 16
A Square változatlanul örökli a describe metódust a Shape osztálytól, és felülírja az area metódust, hogy valódi értéket adjon vissza. A bázis describe metódusa továbbra is működik, mert meghívja a self.area() metódust – ami futási időben a felülírásra oldódik fel.
2.22.2. super()¶
A super() egy olyan proxyt ad vissza, amely lehetővé teszi, hogy egy metódus meghívja egy metódus bázisosztálybeli változatát. A leggyakoribb felhasználás a bázis __init__ meghívása egy alosztály __init__ metódusából, hogy a bázis inicializálhassa a saját attribútumait:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
A super().__init__ elfelejtése a hibák gyakori forrása: a bázis által beállított attribútumok soha nem állítódnak be, az ezekre támaszkodó örökölt metódusok pedig később összeomlanak.
2.22.2.1. Az implicit bázisosztály¶
Minden osztály implicit módon az object osztálytól örököl, amely a Python típushierarchiájának gyökere. Az olyan metódusok, mint a __repr__, a __eq__, és az attribútumhozzáférés mögötti gépezet mind innen származnak, még akkor is, ha egy osztály nem deklarál bázist. A class Foo(object): explicit kiírása és a class Foo: kiírása egyenértékű a modern Pythonban; az utóbbi a szokásos forma.
2.22.3. Mikor segít az öröklődés – és mikor nem¶
Akkor használj öröklődést, amikor az egyik osztály valóban egy másiknak egy specifikusabb fajtája, és a viselkedés nagy részén osztozik. A klasszikus teszt az „is-a” (egy-fajtája) ellenőrzés: egy Square az egy Shape.
Amikor két osztály csak véletlenül osztozik néhány segédfüggvényen, az öröklődés túlzás. Inkább nyúlj kompozícióhoz: az egyik osztály attribútumként tartalmazza a másik egy példányát, és azon az attribútumon keresztül használja. Az alakja „has-a” (van-neki), nem „is-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")
Mindkettő működik. A kompozíciós változat általában jobb, mert:
Kicsiben tartja a felületeket. A
WorkerComposescsak arunés aloggerelemet teszi elérhetővé. AWorkerInheritsalogmetódust is elérhetővé teszi – a hívók közvetlenül írhatják, hogyworker.log(...), akár szándékos volt ez, akár nem.Szétválasztja az élettartamokat. A logger lecserélhető, megosztható a workerek között, vagy workerenként eltérően felépíthető, mindezt a
Workerosztály érintése nélkül. Egy alosztály a bázisához van rögzítve.Elkerüli a metódusnév-ütközéseket. Két olyan bázis, amelyek mindegyike definiál egy
runmetódust, ütközik, amikor egy osztály mindkettőtől próbál örökölni; két attribútum soha nem ütközik.
Egy gyakorlati ökölszabály:
Akkor használj öröklődést, amikor az alosztály valóban egy fajtája a bázisnak, és a bázis minden metódusa rajta is értelmes – a
Squareaz egyShape; aMemoryErroraz egyException.Minden másra használj kompozíciót – amikor csak egy másik osztály viselkedésére van szükséged, nem az identitására. A legtöbb valós kód sokkal többször használ kompozíciót, mint öröklődést.