1. 程式人生 > >Python的程序結構[2] -> 類/class -> 基類與繼承

Python的程序結構[2] -> 類/class -> 基類與繼承

cnblogs times creat logs roman man pos 程序 body

基類與繼承 / Base Class and Inheritance Class


面向對象的特性使得 Python 中不可避免地需要使用到類和類的繼承,類的繼承可以使得代碼很好的被重用。下面以一些代碼示例說明類的繼承如何使用。

繼承一個基類


首先,定義一個基類 Animal,在初始化中設定一個基本屬性以及物種信息,並設置其具有 eat 的能力(self.eat 為 True)。此處還重載了魔術方法 __getattr__,當搜索的屬性不存在時返回 False(即不具備該能力),最後定義一個 show 函數來顯示當前 species 信息。

 1 # ------- Basic inheritance --------
2 class Animal: 3 def __init__(self): 4 self.base = "Creature" 5 self.species = "Animal" 6 self.eat = True 7 8 def __getattr__(self, item): 9 return False 10 11 def show(self): 12 print("This is %s." % self.species)

接著定義一個 Bird 類,繼承自 Animal ,在 Bird 的初始化函數中,首先對基類進行初始化,然後設置 species 屬性,以及 fly True(代表具有 fly 的能力),並對 Bird 定義了 show 函數。同樣定義 Fish 類,設置 species 信息以及 swim 為 True,且不定義 show 函數。

 1 # Note: Do not mix super and Object.__init__
 2 class Bird(Animal):
 3     def __init__(self):
 4         Animal.__init__(self)
 5         self.species = "Bird"
 6         self.fly = True
 7 
 8     def show(self):
 9         print("This is %s from %s." % (self.species, self.base))
10 
11 class Fish(Animal):
12 def __init__(self): 13 Animal.__init__(self) 14 self.species = "Fish" 15 self.swim = True

最後,對兩個類分別進行實例化,並調用各自的 show 函數,

1 b = Bird()
2 f = Fish()
3 b.show()
4 f.show()

輸出的結果如下,

This is Bird from Creature.
This is Fish.

從最終的輸出結果可以看到,

對於 Bird 類,並沒有對 base 屬性進行定義,但卻擁有 base 屬性,即這一屬性從基類 Animal 中繼承得到,

而對於 Fish 類,並未定義 show 方法,但是卻在實例中可以使用 show 方法。同樣這一方法也是繼承自基類 Animal。

這兩個簡單的示例中通過繼承從而避免了重復的代碼和定義。

多繼承


前面的例子中使用的基類是唯一的,當需要從多個基類中繼承時,則會涉及到多繼承問題。下面的代碼給出一個多繼承問題的示例。

首先導入之前定義的兩個類 Bird Fish 作為基類,然後基於這兩個類派生出子類 Duck Goose,其中 Goose 中重載了初始化函數,利用 super 初始化 Goose 的基類。此時,我們希望 Duck Goose 能夠繼承 Bird Fish 的兩個特性,fly swim

Note: 此處為示例代碼,切勿混用 super 和 類的初始化函數。

 1 from inheritance_base import Bird, Fish
 2 # ------- Multi inheritance --------
 3 class Duck(Bird, Fish): pass
 4 
 5 class Goose(Bird, Fish):
 6     def __init__(self):
 7         super(Goose, self).__init__()
 8 
 9 d = Duck()
10 g = Goose()
11 print("I am %s, I can fly: %s" % (d.species, d.fly))
12 print("I am %s, I can swim: %s" % (d.species, d.swim))
13 print("I am %s, I can fly: %s" % (g.species, g.fly))
14 print("I am %s, I can swim: %s" % (g.species, g.swim))

最終的運行結果如下,

I am Bird, I can fly: True
I am Bird, I can swim: False
I am Bird, I can fly: True
I am Bird, I can swim: False

從運行的結果中看到,最後的輸出卻不是我們所期望的,無論是 Duck 的實例還是 Goose 的實例都不具備 swim 的屬性,也就是說,Fish 的初始化並未被執行。

其原因在於,Duck 中並未定義初始化函數,因此會在父類中搜索,從而調用到 Bird 的初始化函數,所以最終顯示 species 屬性為 Bird,而 swim 為 False。而在 Goose 中,使用 super 也未能成功繼承所有屬性,這是由於 super 會依照 MRO 順序進行搜索,使用搜索到的第一個類的對應方法。

為實現所需要的功能,在這裏重新定義一個 Duck 類,重載初始化函數,在初始化函數中分別調用兩個基類的初始化函數。

1 class Duck(Bird, Fish):
2     def __init__(self):
3         Bird.__init__(self)
4         Fish.__init__(self)
5 
6 d = Duck()
7 print("I am %s, I can fly: %s" % (d.species, d.fly))
8 print("I am %s, I can swim: %s" % (d.species, d.swim))

最終輸出結果可以看到,species 屬性被覆蓋了最終顯示為 Fish,而 Goose 也具備了 fly 和 swim 兩個屬性。

I am Fish, I can fly: True
I am Fish, I can swim: True

但這種繼承依舊會引起一些問題。

菱形 / 鉆石繼承


前面的多繼承使用直接調用父類初始化函數進行,表面上能夠滿足多繼承的需求,但是仍然存在一些問題,下面以一個菱形/鉆石繼承來說明這一問題是如何存在的。

首先是定義 A、B、C、D 四個類,這四個類大致為菱形繼承的形式。

"""
    A
    /   /    /     B      C
  \    /
   \  /
    \/
    D
"""

然後以 no-super 和 super 兩種方式來實現這一繼承,
在 no super 中不使用 super,直接使用調用父類初始化函數的方法來完成初始化工作,具體方式如下,

 1 # ------- no super ----------
 2 class A(object):
 3     def __init__(self):
 4         object.__init__(self)
 5         print("This is A init.")
 6 
 7 class B(A):
 8     def __init__(self):
 9         A.__init__(self)
10         print("This is B init.")
11 
12 class C(A):
13     def __init__(self):
14         A.__init__(self)
15         print("This is C init.")
16         
17 class D(B, C):
18     def __init__(self):
19         B.__init__(self)
20         C.__init__(self)
21         print("This is D init.")
22 
23 d = D()

輸出結果如下,從輸出中可以看出,這種方式存在一個問題,那就是對 B 和 C 的基類 A 進行了多次的初始化。這可能會造成一些不期望的結果。

This is A init.
This is B init.
This is A init.
This is C init.
This is D init.

為了避免這種現象,可以使用 super 來完成繼承初始化。

 1 # ------- super ------------
 2 class A(object):
 3     def __init__(self):
 4         super(A, self).__init__()
 5         print("This is A init.")
 6 
 7 class B(A):
 8     def __init__(self):
 9         super(B, self).__init__()
10         print("This is B init.")
11 
12 class C(A):
13     def __init__(self):
14         super(C, self).__init__()
15         print("This is C init.")
16         
17 class D(B, C):
18     def __init__(self):
19         super(D, self).__init__()
20         print("This is D init.")
21 
22 d = D()

使用super的初始化結果顯示如下,可以看到,此時能夠實現對 A 的初始化只進行一次。這是由於 super 中實現了 MRO 的搜索算法。

This is A init.
This is C init.
This is B init.
This is D init.

相關閱讀


1. 魔術方法 __getattr__

2. super

3. MRO 順序

Python的程序結構[2] -> 類/class -> 基類與繼承