目錄python
繼承是面向對象的重要特性之一,是相對兩個類而言的父子關係,子類繼承了父類的全部的屬性和方法,繼承最大的好處是實現了代碼的重用,能夠重用已經存在的數據和行爲,減小代碼的重複編寫。算法
在Python2.2以前,類是沒有共同的祖先的,以後,引入了object類,它是全部類的共同祖先類。Python2中爲了兼容,分爲古典類(舊式類)和新式類。而在Python 3中所有都爲新式類,新式類都是繼承object類的,而且可使用super函數(後面會說)。下面是Python2.x中的代碼設計模式
class A: pass class B(object): pass >>> dir(A) # 查看類的__dict__ ['__doc__', '__module__'] >>> dir(B) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
在Python2.x中 A和B是不一樣的兩個類。A沒有繼承,被稱爲古典類,B繼承自object,被稱爲新式類。不止少了不少方法,連實例對象的屬性也是不太相同的。Python 3中的代碼以下ssh
class A: pass class B(object): pass >>> dir(A) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] >>> dir(B) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] >>>
在Python 3中,都爲新式類,因此A和A(object)是兩個結果相同的不一樣寫法而已 class A:pass == class A(object):pass
。ide
更多的區別這裏就不在詳述,Python 3是將來,忘記舊式類吧。函數
Python在類名後使用一對括號來表示繼承關係,括號中的類即爲父類。先來看看不用繼承的例子:優化
class Animal: def shout(self): print('{} shout'.format(self.__class__.__name__)) class Cat: def shout(self): print('{} shout'.format(self.__class__.__name__)) class Dog: def shout(self): print('{} shout'.format(self.__class__.__name__)) a = Animal() c = Cat() d = Dog() a.shout() c.shout() d.shout()
從上面例子來看,雖然貓狗動物均可以叫,可是卻分別實現了叫這個動做,那麼下一步使用繼承來優化.net
class Animal: def shout(self): print('{} shout'.format(self.__class__.__name__)) class Cat(Animal): pass class Dog(Animal): pass a = Animal() c = Cat() d = Dog() a.shout() c.shout() d.shout()
經過繼承,貓類、狗類就不用寫代碼,直接繼承了父類Animal類的叫方法了。因此,在上面的例子中:設計
和繼承相關的經常使用特殊屬性和方法以下:code
特殊屬性和方法 | 含義 | 示例 |
---|---|---|
__base__ |
類的基類 | |
__bases__ |
類的基類們(元組) | |
__mro__ |
方法解析順序(基類們的元組) | |
mro() |
方法解析順序(基類們的列表) | int.mro() |
__subclasses__() |
類的子類列表 | int.__subclasses__() |
經過一個例子來看繼承中的訪問控制
class Animal: __COUNT = 100 # _Animal__COUNT = 100 HEIGHT = 0 def __init__(self, age,weight,height): self.__COUNT += 1 # self._Animal_COUNT = self._Animal_COUNT + 1 self.age = age self.__weight = weight # self._Animal__weight = weight self.HEIGHT = height def eat(self): print('{} eat'.format(self.__class__.__name__)) def __getweight(self): # def _Animal__getweight(self): print(self.__weight) # print(self._Animal__weight) @classmethod def showcount1(cls): print(cls) print(cls.__dict__) print(cls.__COUNT) # print(cls._Animal__COUNT) @classmethod def __showcount2(cls): # def _Animal__showcount2(cls): print(cls.__COUNT) # print(cls._Animal__COUNT) def showcount3(self): print(self.__COUNT) # print(self._Animal__COUNT) class Cat(Animal): NAME = 'CAT' __COUNT = 200 # _Cat__Count = 200 c = Cat(3,5,15) c.eat() # 1 print(c.HEIGHT) # 2 print(c.__COUNT) # 3 print('c:',c.__dict__) print('cat:',Cat.__dict__) print('Animal:',Animal.__dict__) c.showcount1() # 4 c.showcount2() # 5 c.showcount3() # 6 print(c.NAME) # 7
分析:
'Cat eat'
。15
。沒有該屬性
。Cat類
,cls.__dict__就是Cat.__dict__
,而Cat中只定義兩個屬性,因此值爲{'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
(魔術方法先不考慮),類方法定義在哪一個類中,那麼私有變量就會被改爲以那個類爲前綴的變量名,因此showcount1方法定義在Animal中,__COUNT就變成了_Animal__COUNT。cls.__COUNT就是在尋找cls._Animal__COUNT屬性,而Cat類中的__COUNT被更名爲_Cat__COUNT,因此最後只能在Animal中找到,因此值爲100
。沒有該屬性方法
。showcount3定義在Animal中,因此self.__COUNT實際上爲self._Animal__COUNT,c在實例化的同時進行了初始化,Cat沒了__init__函數,因此繼承了父類Animal的init,在初始化過程當中定義的self.__COUNT,實際上就是self._Animal__COUNT,這裏self._Animal__COUNT = self._Animal__COUNT + 1,先算等式右邊,在執行時c尚未_Animal__COUNT屬性,因此會從Cat類開始找直到Animal類,Cat類的__COUNT更名爲了_Cat__COUNT,不是咱們想要的,而後找到了Animal的100,而後加1,再賦給實例c(等於c._Animal__COUNT = Animal._Animal__COUNT + 1),因此實例c來講,它本身就已經擁有_Animal__COUNT屬性,它的值爲101
。
通常狀況下不會這麼寫,這裏只是練習知識點。分析這種狀況時,直接把私有變量/方法更名後就很是好分析了。
總結:
實例屬性查找順序:實例的__dict__ , 類的__dict__ , 父類的__dict__(若是有繼承)。
遇到私有變量/方法看定義的位置,直接進行更名就比較好分析了。
方法重寫,顧名思義就是重寫繼承來的方法,Python和其餘語言不一樣的是,在Java中,要重寫的方法,參數數量和類型要和原方法徹底相同才行,不然會被認爲是方法重載。Python因爲其動態的語言的特性,只要方法相同,則表示的就是方法重寫。
class Animal: def __init__(self, name): self.name = name def shout(self): print('Animal shout') class Cat(Animal): def shout(self): print('miaomiao') c = Cat('daxin') c.shout()
上面的例子中,Cat繼承了Animal類的shout方法,可是因爲Animal中定義的shout不符合Cat的需求,因此在Cat中重寫了shout方法,可是須要注意的是,重寫不是覆蓋,嚴格來講的話應該算是遮蓋,由於Python的類查找順序是按照當前實例->父類->基類等來的,因此當給Cat類定義shout方法後,實例調用方法shout時,父類中已經包含了shout方法,因此就直接調用了,而Animal中的shout依舊在,只是Cat的shout方法被預先發現了。
若是咱們並非要改寫,而是要加強原方法的功能呢?
class Animal: def shout(self): print('Animal shout') class Cat(Animal): def shout(self): self.__class__.__base__.shout(self) # 須要手動傳入self print('miaomiao') c = Cat() c.shout()
經過查找父類而後傳入實例調用,是能夠的,可是不建議這樣使用,在這種狀況下,咱們通常會使用super
.
super
函數(類)是用於調用父類(超類)的一個方法的。主要的兩種寫法以下:
super() # 指代父類 super(type, obj) # 一樣指代父類,super接受兩個參數,第一個是類型,第二個是實例對象。 super() == super(type, obj)
super(type, obj) 通常寫爲super(self.__class__, self) 按照上面Animal的例子的話,就爲super(Cat, self)
利用super完成加強方法的例子:
class Animal: def shout(self): print('Animal shout') class Cat(Animal): def shout(self): super(Cat, self).shout() # super().shout() print('miaomiao') c = Cat() c.shout()
靜態方法和類方法均可以被覆蓋,原理都相同,都是在屬性字典__dict__中搜索。
class Animal: def __init__(self, name): self.name = name def shout(self): print('Animal shout') class Cat(Animal): def __init__(self, age): self.age = age def shout(self): print('miaomiao') c = Cat('daxin',20) c.shout() print(c.name)
請問這種狀況是能夠執行嗎?答案是不行的,由於Cat重寫了__init__方法,因此在c實例化時,只能訪問Cat類的__init__方法,因此,就須要顯示的調用父類的__init__方法。
class Animal: def __init__(self, name): self.name = name def shout(self): print('Animal shout') class Cat(Animal): def __init__(self, name, age): super(Cat, self).__init__(name) # 等於把實例和name變量,傳遞給Animal.__init__(self,name) # super().__init__(name) # 效果相同 self.age = age def shout(self): print('miaomiao') c = Cat('daxin',20) c.shout()
須要注意的是,若是有同名屬性,那麼後執行的會覆蓋先執行的。
def __init__(self, name, age): super(Cat, self).__init__(name) self.name = age # 覆蓋父類初始化的name屬性
另外在私有屬性繼承的狀況下,請注意真正的變量名稱(由於會更名)。
一個類繼承自多個類就是多繼承,它將具備多個類的特徵。面向對象的設計的開閉原則(實體應該對擴展開放,而對修改封閉),就能夠利用繼承來設計,即多用'繼承',少修改(並非通常的多繼承,後面會詳述)。它的定義個數以下
class MyClass(A,B,...):pass
Python中的繼承關係,分爲多繼承和單繼承,如圖所示:
多繼承很好的模擬了世界,由於事物不多是單一繼承,可是捨棄簡單,必然引入複雜性,帶來了衝突。舉個簡單的例子:若是一個孩子繼承了來自父母雙方的特稱,那麼到底眼睛像爸爸仍是媽媽呢,孩子究竟像誰多一點呢?
多繼承的實現或致使編譯器設計的複雜度增長,因此如今不少語言也捨棄了類的多繼承。
C++支持多繼承,而Java捨棄了多繼承。有些人說Java支持的多繼承的,其實他說的是接口,在Java中,一個類能夠實現多個接口,一個接口也能夠繼承多個接口。Java的接口很純粹,只是方法的生命,繼承者必須實現這些方法,就具備了這些能力,就能幹什麼。
多繼承帶來的問題,最主要的就是二義性,例如貓和狗都繼承自動物類,如今若是一個類繼承了貓和狗類,貓和狗都有shout方法,子類究竟繼承誰的shout呢?
實現多繼承的語言,要解決二義性,主要有兩種方法
深度優先
和廣度優先
。
MRO:方法解析順序,Python使用MRO來解決多繼承帶來的二義性問題。由於Python 2.x的舊式類和新式類等歷史緣由(舊式類不會繼承object對想),MRO有三個搜索算法:
MyClass、D、B、A、C
MyClass、D、B、C、A、object
MyClass、D、B、C、A、object
經典算法有很大的問題。若是C中有覆蓋A的方法,就不會訪問到C,由於深度優先,會先訪問A。新式類算法算法,依然採用了深度優先,解決了重複問題,可是同經典算法同樣,麼有解決繼承和單調性的問題。
單調性:若是在D的解析順序中,B排在A的前面,那麼在D的全部子類裏,也必須知足這個順序。
C3算法,解決了繼承的單調性,它阻止建立以前版本產生的二義性代碼,求得MRO是爲了線性化,且肯定了順序。關於MRO和C3能夠參考:Python的多重繼承問題-MRO和C3算法
class A:pass class B(A):pass class C(A):pass class D(B):pass class MyClass(D,C):pass print(MyClass.mro()) # [<class '__main__.MyClass'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
注意:
當類不少,繼承很複雜的狀況下,繼承路徑太多,很難說清什麼樣的繼承路線。Python語法是容許多繼承的,可是Python代碼是解釋執行,只有執行時,才發現錯誤,若是團隊協做開發,而且引入多繼承,那麼代碼將有可能會變得不可控。因此在Python平常開發規範中建議:
從一個需求開始瞭解Mixin。現有以下繼承關係:
假設已經有了三個類:
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() # raise表示拋出異常,NotImplementedError表示沒有被實現。 class Human(Animal): pass class Monkey(Animal): pass
解釋:
沒有實現的方法稱爲抽象方法,擁有抽象方法的類,叫作抽象類(抽象類不是用來實例化的,而是用來繼承的,因此又叫作抽象基類)
子類直接執行talk方法時會產生異常(方法沒有被實現)
Python中若是採用上面的方式定義抽象方法,子類能夠不實現,可是到子類使用該方法的時候纔會報錯。
Animal類是抽象基類,基類的方法能夠不具體實現,由於它未必適合全部子類,在子類中通常須要重寫。Human類和Monkey類屬於Animal的子類,如今須要爲Human類添加說話的功能,該怎麼辦呢?若是在Humman類上直接添加,雖然能夠,可是卻違反了OCP原則,因此咱們只能繼承了
下面對代碼進行改寫
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() class Human(Animal): pass class Monkey(Animal): pass class TalkHuman(Human): def talk(self): print('{} say something'.format(self.name)) daxin = TalkHuman('daxin') daxin.talk()
疑問:看似完成了需求,可是若是Human又要唱歌、跳舞、吃飯等方法呢?每次都要繼承嗎?這樣類會不會太多了?可否用其餘的方法呢?
前面咱們利用裝飾器爲函數新增了功能,在Python中一切皆對象,函數和類都是對象,那麼咱們是否能夠利用裝飾器爲類添加新功能呢?答案固然是能夠的。使用裝飾器爲Human類添加talk方法:
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() def talkhuman(cls): # def talk(self): # print('{} say I Love You'.format(self.name)) # cls.talk = talk cls.talk = lambda self: print('{} say I Love You'.format(self.name)) return cls @talkhuman # Human = talk(human) class Human(Animal): pass class Monkey(Animal): pass daxin = Human('daxin') daxin.talk()
使用柯里化很容易就能夠寫出爲類添加方法的裝飾器,這種裝飾器還有一個好處,哪裏須要talk功能,直接裝飾就好。有多個功能的話,那就寫多個裝飾器。
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() def talkhuman(cls): cls.talk = lambda self: print('{} say I Love You'.format(self.name)) return cls def sleephuman(cls): cls.sleep = lambda self: print('{} will sleep with you'.format(self.name)) return cls @sleephuman @talkhuman class Human(Animal): pass class Monkey(Animal): pass daxin = Human('daxin') daxin.talk() daxin.sleep()
先來看代碼:
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() class TalkMixin: def talk(self): print('{} say I Love You too'.format(self.name)) class Human(Animal): pass class Monkey(Animal): pass class TalkMan(TalkMixin, Human): pass daxin = TalkMan('daxin') daxin.talk()
PS: 感受就是寫了一個類給別人繼承了?
Mixin體現的就是一種組合的設計模式
,本質上就是多繼承實現的。核心思想就是把其它類混合進來,同時帶來了類的屬性和方法。這裏的Mixin看起來和裝飾器的效果是同樣的,也沒什麼特別的,可是Mixin是類,它能夠繼承。
class Animal: def __init__(self, name): self.name = name def talk(self): # 抽象方法,沒有真正被實現,須要子類本身去實現 raise NotImplementedError() class TalkMixin: def talk(self): print('{} say I Love You'.format(self.name)) class SingMixin(TalkMixin): # 經過繼承來添加新的功能 def sing_a_song(self): print('{} want sing a song'.format(self.name)) super(SingMixin, self).talk() print('Go out,Now') class Human(Animal): pass class Monkey(Animal): pass class TalkMan(TalkMixin, Human): pass class SingMan(SingMixin,Human): pass daxin = SingMan('daxin') daxin.sing_a_song()
使用原則:
使用時Mixin類一般在繼承列表的第一個位置
。小結: