第12章–繼承的優缺點
本章探討繼承和子類化,重點是說明對 Python 而言尤爲重要的兩個細節:
• 子類化內置類型的缺點–例如寫一個繼承list的列表類
• 多重繼承和方法解析順序
子類化內置類型
結論:不要子類化內置類型,而是選擇子類化collections模塊對應的類比如UserDict UserList UserStr
原因:內置類型比如list、dict使用c語言編寫的,內置類型不會調用用戶定義的類覆蓋的特殊方法
內置類型的子類覆蓋的方法會不會隱式調用,CPython 沒有制定官方規則。基本上,內置類型的方法不會調用子類覆蓋的方法。例如,dict 的子類覆蓋的
getitem() 方法不會被內置類型的 get() 方法調用。
具體代碼對比
from collections import UserDict
class ADict(dict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
class BDict(UserDict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
ad=ADict(name='jason')
print(ad) #{'name': 'jason'}
ad['age']=64
print(ad) # {'name': 'jason', 'age': [64, 64]}
ad.update(job='student')
print(ad) # {'name': 'jason', 'age': [64, 64], 'job': 'student'}
bd=BDict(name='jason')
print(bd) # {'name': ['jason', 'jason']}
bd['age']=64
print(bd) # {'name': ['jason', 'jason'], 'age': [64, 64]}
bd.update(job='student')
print(bd) # {'name': ['jason', 'jason'], 'age': [64, 64], 'job': ['student', 'student']}
ADict和BDict的setitem方法是一樣的,把傳進來的value設置成列表形式,ADict繼承內置類型dict,BDict繼承自UserDict,
ADict類型的init和update函數不會發現用戶定義的setitem函數,而是繼續用dict的setitem函數,導致了value的值的類型不一致,有的是非列表有的是列表,這不是用戶願意看到的
BDict類型的init和update函數都用到了用戶定義的setitem函數,整個類實例的行爲看上去都是一致的
多重繼承和方法解析順序
python支持多重繼承
super()調用父類的方法會根據方法解析順序(MRO)的順序來調用方法
每個類都有一個內置屬性__mro__,是一個元祖,會按照MRO列出各個父類,調用方式是CLassName.__mro__,而不是類的實例
如果想調用特定的父類的方法,可以通過ParentClass.fun(child_instance),把子類的實例傳給父類的方法
class A():
def fun(self):
print("A fun of ",self)
class B(A):
def fun(self):
print("B fun of ",self)
class C(A):
def fun(self):
print("C fun of ",self)
class D(B,C):
def fun(self):
super().fun()
d=D()
d.fun() # B fun of <__main__.D object at 0x00000286500AD438>
print("MRO of Class D ",D.__mro__)
# MRO of Class D (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
C.fun(d) # C fun of <__main__.D object at 0x0000020B7CA3D470>
A.fun(d) # A fun of <__main__.D object at 0x000001FE08FED470>