面向對象編程過程中,我們可以從已有class繼承,定義一個新的類class,這個類被稱作子類Subclass,被繼承者的類被稱為基類、父類或者超類(Base class、Super class)。例如我們編寫了一個名為Animal的類:
<code>>>> class Animal(object): def run(self): print('Animals are running...') >>> animal = Animal() >>> animal.run() Animals are running.../<code>
當我們定義新的類Dog和Cat時,可以直接從Animal繼承:
<code>>>> class Dog(Animal): pass >>> class Cat(Animal): pass >>> dog = Dog() >>> dog.run() Animals are running... >>> cat = Cat() >>> cat.run() Animals are running.../<code>
對於Dog和Cat,Animal就是它們的父類,它倆就是Animal的子類,都繼承了Animal的run方法當然可以對子類增加方法:
<code>>>> class Dog(Animal): def run(self): print('Dog is running...') def eat(self): print('Dog is eating...')/<code>
相比直接從Animal繼承的run,顯然修改後的Dog類的run方法更符合邏輯。當子類和父類都存在相同方法run()時,子類的run覆蓋了父類的run。
多態
當我們定義一個class時,實際上就是定義了一種數據類型,接下來我們看下面這組判斷:
<code>>>>a = list() >>>b = Animal() >>>c = Dog() >>> isinstance(a, list) True >>> isinstance(b, Animal) True >>> isinstance(c, Dog) True >>> isinstance(c, Animal) True/<code>
isinstance用來判斷一個變量是否為某個類型,我們看到a,b,c都符合對應的類型;但是c除了對應Dog類型,同時也是Animal類型。如上,在繼承關係中,如果一個實例的數據類型是一個子類,它的數據類型也可以看做父類,但反過來是不行的:
<code>>>> b = Animal() >>> isinstance(b, Dog) False/<code>
要理解多態的好處,我們還需要再編寫一個函數,這個函數接受一個Animal類型的變量:
<code>>>> def run_twice(animal): animal.run() animal.run() >>> run_twice(Animal()) Animals are running... Animals are running... >>> run_twice(Dog()) Dog is running... Dog is running.../<code>
我們再定義一個Animal的子類Mouse:
<code>>>> class Mouse(Animal): def run(self): print('Mouse is so small...') >>> run_twice(Mouse()) Mouse is so small... Mouse is so small.../<code>
可以看到對於新增的子類Mouse,不必修改run_twice,依賴父類Animal作為參數的函數或方法都可以不加修正的正常使用,原因就是多態!多態的好處就在於此,我們要傳入子類時,只要接收父類就可以,例如Dog、Cat都是Animal類型。對於一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
1、對擴展開放:允許新增Animal子類;
2、對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關係。而任何類,最終都可以追溯到根類object,這些繼承關係看上去就像一顆倒著的樹。比如如下的繼承樹:
<code> ┌───────────────┐ │ object │ └───────────────┘ │ ┌────────────┴────────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Animal │ │ Plant │ └─────────────┘ └─────────────┘ │ │ ┌─────┴──────┐ ┌─────┴──────┐ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Cat │ │ Tree │ │ Flower │ └─────────┘ └─────────┘ └─────────┘ └─────────┘/<code>
靜態語言 vs 動態語言
對於靜態語言(如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
<code>class Timer(object): def run(self): print('Start...')/<code>
這就是動態語言的“鴨子類型”,它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。Python的“file-like object“就是一種鴨子類型。對真正的文件對象,它有一個read()方法,返回其內容。但是,許多對象,只要有read()方法,都被視為“file-like object“。許多函數接收的參數就是“file-like object“,你不一定要傳入真正的文件對象,完全可以傳入任何實現了read()方法的對象。
小結
繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫。動態語言的鴨子類型特點決定了繼承不像靜態語言那樣是必須的。