浅谈python使用多态跟不用多态的区别_python 多态和 super 用法
python 中的多態實現非常簡單,只要是在子類中實現和父類同名的方法,便能實現多態,如果想在子類中調用父類的方法,有多種方法,但是當涉及菱形繼承等問題是,super 就成為了比較好的解決方案。
普通繼承
對于比較簡單的繼承關系,通常在子類中有兩種方法來執行父類的方法,示例如下。
基類:
1
2
3class Base(object):
def __init__(self):
print("init Base")
示例 1:
1
2
3
4
5
6
7class A(Base):
def __init__(self):
# 通過父類顯式執行
Base.__init__(self)
print("init A")
a = A()
輸出:
1
2init Base
init A
示例 2:
1
2
3
4
5
6
7class B(Base):
def __init__(self):
# 調用 super
super(B, self).__init__()
print("init B")
b = B()
輸出:
1
2init Base
init B
可以看到,兩種方法都可以調用父類的方法對父類進行初始化。需要注意的是,兩種方法都要傳入 self,但是在子類中調用父類的 super 中傳入的 self是子類對象。
菱形繼承
當有多重繼承,特別是菱形繼承時,這兩種方法就有區別了,示例如下。
示例 1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Base(object):
def __init__(self):
print("init Base")
class A(Base):
def __init__(self):
Base.__init__(self)
print("init A")
class B(Base):
def __init__(self):
Base.__init__(self)
print("init B")
class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print("init C")
c = C()
輸出:
1
2
3
4
5init Base
init A
init Base
init B
init C
可以看到,Base 被 init 了兩次,至于其缺陷,在 C++ 中就已經討論過了,反正就是不符合我們的預期,不想這種實現。C++ 中通過虛繼承解決菱形繼承問題,在 python 中可以使用 super 規避這種缺陷。
示例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Base(object):
def __init__(self):
print("init Base")
class A(Base):
def __init__(self):
super(A, self).__init__()
print("init A")
class B(Base):
def __init__(self):
super(B, self).__init__()
print("init B")
class C(A, B):
def __init__(self):
super(C, self).__init__()
print("init C")
c = C()
輸出
1
2
3
4init Base
init B
init A
init C
運行這個新版本后,你會發現每個 __init__() 方法只會被調用一次了。
為了弄清它的原理,我們需要花點時間解釋下 python 是如何實現繼承的。對于你定義的每一個類,python 會計算出一個所謂的方法解析順序(MRO)列表。 這個 MRO 列表就是一個簡單的所有基類的線性順序表。我們可以看一下 C 的 MRO 表。
1
2>>>C.__mro__
(__main__.C, __main__.A, __main__.B, __main__.Base, object)
為了實現繼承,python 會在 MRO 列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個 MRO 列表的構造是通過一個 C3 線性化算法來實現的。 我們不去深究這個算法的數學原理,它實際上就是合并所有父類的 MRO 列表并遵循如下三條準則:
子類會先于父類被檢查
多個父類會根據它們在列表中的順序被檢查
如果對下一個類存在兩個合法的選擇,選擇第一個父類
必須牢記:MRO 列表中的類順序會讓你定義的任意類層級關系變得有意義。
當使用 super() 函數時,python 會在 MRO 列表上繼續搜索下一個類(這是一種嵌套實現)。 只要每個重定義的方法統一使用 super() 并只調用它一次, 那么控制流最終會遍歷完整個 MRO 列表,每個方法也只會被調用一次。 這也是為什么在第二個例子中你不會調用兩次 Base.__init__() 的原因。換句話說,super 調用了次且僅有一次所有的父類。
由于 super 遞歸調用的會繼續搜索的特性,可能會出現一些意向不到的效果,比如下面這個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class A(object):
def spam(self):
print('A.spam')
super(A, self).spam()
class B(object):
def spam(self):
print('B.spam')
class C(A, B):
pass
c = C()
c.spam()
C.__mro__
輸出
1
2
3A.spam
B.spam
(__main__.C, __main__.A, __main__.B, object)
為啥 c.spam() 會同時調用 A 和 B 的 spam()?其實看到 MRO 順序就明白了:
首先在 c 的類 C 中查找 spam 方法,沒有找到就查找 A 中的 spam 方法。
調用 A 中的 spam 方法,然后遇到 A 的 super 調用,繼續在 MRO 順序表中查找 spam 方法。注意,這里本來調用了 A 的 spam 就應該返回的,但是 super 的存在,導致了繼續遞歸。
遇到 B 的 spam 方法,調用,結束。
super 的使用
對于 python2 和 python3,super 的用法有一些區別:
原因:
python2 沒有默認繼承 object
python3 默認全部繼承 object 類,都是新式類
用法區別:
python2: super(開始類名,self).函數名()
python3:super().函數名()
參考資料
總結
以上是生活随笔為你收集整理的浅谈python使用多态跟不用多态的区别_python 多态和 super 用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 托盘图标菜单_全新开始菜单和任务栏,Wi
- 下一篇: 仪表指针样式_Qt自定义Widget之仪