我們在使用python中的類繼承時,子類繼承父類後,在重載父類的方法後,在方法中如果要執行父類對應的方法,一般有兩種方式:super和Base(表示父類名)。
使用例子
先看下面一段代碼:
# -*- coding: utf-8 -*- class Base: def __init__(self): self.postion = (0, 0)
def move(self, x, y): self.postion = (self.postion[0] + x, self.postion[1] + y)
class Device(Base): def __init__(self): super(Device, self).__init__() self.offset = (0, 0)
# 記錄本次位置偏移量 def move(self, x, y): self.offset = (self.postion[0] – x, self.postion[1] – y) super(Device, self).move(x, y) def get_offset(self): return self.offset
在Base類中,有一個move方法用來移動位置,在子類中我們想要增加一個記錄,記住每次移動的偏移量,那麽我們就在子類中重載move函數,執行我們自定義的操作(記錄偏移量),然後繼續執行父類的move動作,直接調用父類move函數就可以避免我們重新實現移動位置的動作。
在上面的例子中,我們使用了super來調用父類方法,那麽能不能使用Base來調用呢?
…. Base.__init__(self) …… Base.move(self, x, y) ….
可以看到Base調用父類函數時,必須在函數中傳遞self參數。之前的文章中我們了解到類的普通函數調用需要使用類對象調用,而類的普通函數第一個參數默認是self,調用時不需要傳遞此參數,因爲通過對象調用會自動傳遞。但是直接使用Base類名調用時,方法內部需要知道self是誰。那麽兩種方式都可以,他們有區別嗎?當然是有的
首先看一下super的定義
class super(object): “”” super() -> same as super(__class__, <first argument>) super(type) -> unbound super object super(type, obj) -> bound super object; requires isinstance(obj, type) super(type, type2) -> bound super object; requires issubclass(type2, type) Typical use to call a cooperative superclass method: class C(B): def meth(self, arg): super().meth(arg) This works for class methods too: class C(B): @classmethod def cmeth(cls, arg): super().cmeth(arg) “””
可以看到,super有四種調用方式
- super(): 相當于super(當前類名, 第一個參數) python3中新增的方式
- super(type):沒有綁定對象
- super(type, obj):綁定對象,要求obj的類型是type或者其子類
- super(type, type2):綁定對象,要求type2是type的子類
這裏我們就先說一下super()和super(type, obj),這是我們常用的方式
在上面的例子中我們看到super和Base的方式一樣,接下來我們再看一個例子
# -*- coding: utf-8 -*- class Base: def __init__(self): print(“Base”) self.name = “Base”
class Device1(Base): def __init__(self): Base.__init__(self) print(“Device1”) self.name1 = “Device1”
class Device2(Base): def __init__(self): Base.__init__(self) print(“Device2”) self.name2 = “Device2”
class Sub(Device1, Device2): def __init__(self): Device1.__init__(self) Device2.__init__(self) print(“Sub”) self.name3 = “Sub”
def test(self): print(“test: “, self.name2)
s = Sub() s.test()
# 輸出: Base #Base.__init__中的print Device1 #Device1.__init__中的print Base #Base.__init__中的print Device2 #Device2.__init__中的print Sub #Sub.__init__中的print test: Device2 #test方法中的print
四個類,Base初始化函數被調用了兩次,爲什麽呢?Sub.__init__中Device1和Device2都調用了初始化方法,是這個原因嗎?准確點來講,是的,可不可以只調用一個,那麽Base就只會被調用一次喽,如果只調用Device1.__init__會有什麽結果?
Base Device1 Sub Traceback (most recent call last): File “/Users/small_bud/Desktop/Python/SpiderProjects/淘寶搜索/__init__.py”, line 30, in <module> s.test() File “/Users/small_bud/Desktop/Python/SpiderProjects/淘寶搜索/__init__.py”, line 27, in test print(“test: “, self.name2) AttributeError: ‘Sub’ object has no attribute ‘name2’
沒有name2屬性,而且Device2的初始化函數__init__沒有調用,所以name2屬性沒有被定義,那如果把Base換成super,結果怎麽樣呢?
# -*- coding: utf-8 -*- class Base: def __init__(self): print(“Base”) self.name = “Base”
class Device1(Base): def __init__(self): super().__init__() print(“Device1”) self.name1 = “Device1”
class Device2(Base): def __init__(self): super().__init__() print(“Device2”) self.name2 = “Device2”
class Sub(Device1, Device2): def __init__(self): super().__init__() print(“Sub”) self.name3 = “Sub”
def test(self): print(“test: “, self.name2)
s = Sub() s.test()
# 輸出: Base #Base.__init__中的print Device2 #Device2.__init__中的print Device1 #Device2.__init__中的print Sub #Sub.__init__中的print test: Device2 #test方法中的print
這下看起來完美了,改調的都調了,不該調的沒調,看起來super才是正確的使用方式。那爲什麽會有這兩中差異呢?我要是不說點有內容的東西
靈魂畫手,湊合看吧。我們類的每一個函數首地址會被存儲起來,當我們用類名去調用一個函數的時候,我們可以理解爲它代表一個絕對的地址,可以絕對定位到函數的地址。這個時候你可以把它理解爲一個普通函數def Base.__init__(self),函數名是Base.__init__,參數是self
# 這是一個測試類,只爲創建一個空對象 class Test: pass
class Base: def __init__(self): self.name = “aaaa”
def func(obj): obj.age = 111
t = Test() Base.__init__(t) print(t.name) func(t) print(t.age)
#輸出: aaaa 111
看到了,Base.__init__和func是一樣的,這絕不是我們所希望的類函數。那麽爲什麽super會正確的找到要執行的函數呢?看一下下面的例子:
class A: def __init__(self): print(“A”)
class B: def __init__(self): super().__init__() print(“B”)
class C(B, A): def __init__(self): super().__init__() print(“C”)
class D(C): def __init__(self): super().__init__() print(“D”)
D() print(D.mro())
#輸出: A B C D [<class ‘__main__.D’>, <class ‘__main__.C’>, <class ‘__main__.B’>, <class ‘__main__.A’>, <class ‘object’>]
我們知道子類的對象初始化時,將先執行父類的初始化,然後才執行子類初始化,從初始化打印信息也可以看出來,A>B>C>D,再看一下mro()函數的打印信息,這裏展示了當前類及其父類的類名,我們可以這樣理解每一個類被定義後,其存儲在程序存儲區,除了類方法,還存在一個繼承管理表,表中按照一定的順序存儲著類及其父類的類名,類的初始化就按照表中的順序進行初始化,按照以上的順序object>A>B>C>D,從object開始初始化,然後是A、B、C、D。之前Base.__init__的調用我們分析過了,現在super這種方式呢?它是根據mro列表中記錄的類,按照順序依次調用,這樣就不會出現一個類被重複調用的情況。MRO列表是通過一種算法計算出順序的,我們不用關注它,不過要記住以下幾個准則:
- 子類會先于父類被檢查子類一定是在列表的第一位
- 多個父類會根據它們在列表中的順序被檢查C繼承自A、B,A和B哪個先被調用,根據他們在表中的順序
- 如果對下一個類存在兩個合法的選擇,選擇第一個父類
總結
通過上面的分析,我們知道在類繼承中,一定要使用super的方式才能正確調用類繼承關系,在python3中推薦使用super().__init__,pytho2中使用super(Base, self).__init__