python繼承問題 python繼承細節

2021-10-11 16:47:12 字數 3980 閱讀 2834

不要子類化內建型別

內建型別(由c語言編寫)不會呼叫使用者定義的類覆蓋的特殊方法。

例如,子類化dict作為測驗:

classdoppedict(dict):def __setitem__(self, key, value):

super().__setitem__(key, [value]*2) #改為重複存入的值

dd= doppedict(one=1)print(dd)

dd['two'] = 2

print(dd)

dd.update(three=3)print(dd)#結果

#沒有預期效果,即__init__方法忽略了覆蓋的__setitem__方法

#正確呼叫

#update方法忽略了覆蓋的__setitem__方法

原生型別這種行為違背了物件導向程式設計的乙個基本原則:始終應該從例項所屬的類開始搜尋方法,即使在超類實現類的呼叫也是如此。這種環境中,有個特例,即__miss__方法能按預期工作。

不止例項內部的呼叫有這個問題,,內建型別的方法呼叫其他類的方法,如果被覆蓋了,也不會被呼叫。例如:

classanswerdict(dict):def __getitem__(self, item): #不管傳入什麼鍵,始終返回42

return 42ad= answerdict(a='foo')print(ad['a'])

d={}

d.update(ad)print(d['a'])print(d)#結果

42 #符號預期

foo #update忽略了覆蓋的__getitem__方法

因而子類化內建型別(dict,list,str)等容易出錯,內建型別的方法通常會忽略使用者覆蓋的方法。

不要子類化內建型別,使用者自定義的類應該繼承collections模組中的類,例如userdict,userlist,userstring,這些類做了特殊設計,因此易於擴充套件:

importcollectionsclassanswerdict(collections.userdict):def __getitem__(self, item): #不管傳入什麼鍵,始終返回42

return 42ad= answerdict(a='foo')print(ad['a'])

d={}

d.update(ad)print(d['a'])print(d)#結果沒有問題

42多重繼承和方法解析順序

定義四個類abcd:

classa:defping(self):print('a_ping:', self)classb(a):defpong(self):print('b_pong:', self)classc(a):defpong(self):print('c_pong:', self)classd(b, c):defping(self):

super().ping()print('d_ping:', self)defpingpong(self):

self.ping()

super().ping()

self.pong()

super().pong()

c.pong(self)

實箭頭表示繼承順序,虛箭頭表示方法解析順序,如圖所示:

在d例項上呼叫pong方法:

if __name__ == '__main__':

d=d()print(d.pong())print(c.pong(d))#結果

b_pong: <__main__.d object at>c_pong:<__main__.d object at>

按照解析順序,,直接呼叫d.pong()執行的是b類中的版本;

超類中的方法都可以呼叫,只要把例項作為顯式引數傳入,如上面的c.pong(d)

python能區分d.pong()呼叫的是哪個方法,是因為python會按照特定的順序便利繼承圖。這個方法叫做方法解析順序(本例中的解析順序如虛箭頭所示)。類都有乙個名為__mro__的屬性,它的值是乙個元組,按照方法解析順序列出各個超類,從當前類一直往上,直到object。d類的__mro__:

d->b->c->a->object

defping(self):

a.ping(self)print('d_ping:', self)

這樣就呼叫了a的ping()方法繞過了b。

if __name__ == '__main__':

d=d()print(d.ping())#結果,兩次呼叫#1呼叫super().ping(),super()函式把ping呼叫委託給a類(b,c沒有ping方法)#2呼叫print('d_ping',self)

a_ping: <__main__.d object at>d_ping:<__main__.d object at>

pingpong方法的5個呼叫:

if __name__ == '__main__':

d=d()print(d.pingpong())#結果#1呼叫self.ping#2呼叫self.ping內部的super.ping#3呼叫super().ping#4呼叫self.pong(),根據__mro__,找到b的pong#5呼叫super().pong(),根據__mro__,找到b的pong#6呼叫c.pong(self),忽略__mro__,呼叫c類的pong

a_ping: <__main__.d object at>d_ping:<__main__.d object at>a_ping:<__main__.d object at>b_pong:<__main__.d object at>b_pong:<__main__.d object at>c_pong:<__main__.d object at>

方法解析順序不僅考慮繼承圖,還考慮子類宣告中列出的超類的順序。如果把d類宣告為class d(c, b):, 那麼__mro__中就是:d->c->b->a->object

分析類時檢視__mro__屬性可以看到方法解析順序:

bool.__mro__(, , )importnumbers

numbers.integral.__mro__(, , , , , )#base結尾命名的是抽象基類

importio

處理多重繼承

一些建議:

1.把介面繼承和實現繼承區分開

使用多重繼承時,一定要明確一開始為什麼要建立子類。主要原因可能有:

1)實現介面,建立子型別,實現"是什麼"關係

2)繼承實現,通過重用避免**重複

這兩條可能同時出現,不過只要可能,一定要明確意圖。通過繼承重用**是實現細節,通常可以換用組合和委託模式。而介面繼承則是框架的支柱。

2.使用抽象基類顯式表示介面

如果類作用是定義介面,應該明確把它定義為抽象基類,建立abc.abc或其他抽象基類的子類。

3.通過混入重用**

如果乙個類作用是為多個不相關的子類提供方法實現,從而實現重用,但不體現"是什麼"關係,應該明確把那個類定義為混入類。混入類不能例項化,具體類不能只繼承混入類。混入類應該提供某方面特定行為,只實現少量關係非常緊密的方法。

4.在名稱中明確指明混入

在名稱中加入mixin字尾。

5.抽象基類可以作為混入,反過來則不成立

抽象基類可以實現具體方法,因此也可以作為混入使用。不過,抽象基類會定義型別,而混入做不到。此外,抽象基類可以作為其他類的唯一基類,而混入類不行。

抽象基類有個侷限而混入類沒有:抽象基類中實現的具體方法只能與抽象基類以及其超類中的方法協作。

6.不要子類化多個具體類

具體類可以沒有或者最多只有乙個具體超類。也就是說,具體類的超類中除了這乙個具體超類之外,其餘都是抽象基類或者混入。例如,下列**中,如果alpha是具體類,那麼beta和gamma必須是抽象基類或者混入:

classmyconcreteclass(alpha, beta, gamma):#更多**...

7.為使用者提供聚合類

類的結構主要繼承自混入,自身沒有新增結構或者行為,那麼這樣的類稱為聚合類。

8.優先使用物件組合而不是繼承

組合和委託能夠代替混入,把行為提供給不同的類,但是不能取代介面繼承去定義型別層次結構。

以上來自《流暢的python》

python繼承問題 Python繼承問題

我實際上遇到問題的 很長,所以我做了乙個顯示我的問題的例子.我有兩個繼承自基類 baseclass 的類.這兩個類都為self.dict新增了一些元素.然而,它們似乎交叉汙染元素.我期待c0.dict返回和c1.dict返回.但是它們都返回.為什麼它們會交叉汙染?class baseclass de...

python多繼承 super問題

coding utf 8 胖子老闆的父類 class fatfather object def init self,name,args,kwargs print print 開始呼叫 fatfather print fatfather的init開始被呼叫 self.name name print 呼...

Python類的繼承問題

話不多說,直接上 class parent object x 1class child1 parent pass class child2 parent pass print parent.x,child1.x,child2.x child1.x 2print parent.x,child1.x,c...