天天看點

python中super的了解_super的了解和使用【轉載】

原文連結 https://www.jianshu.com/p/ff7e651e241a

1.super是一個類,傳回的是一個 proxy對象,目的是可以讓你通路父類的一些特殊方法

2.你得按照父類對應的特殊方法去傳遞參數,父類沒有的參數就不要亂傳

3.不要一說到 super 就想到父類!super 指的是 MRO(method resolution order) 中的下一個類!

另一篇比較好的文章:了解 Python super

1. python2 子類調用父類函數成員有2種方法:普通方法和super()方法

假設Base是基類

class Base(object):

def __init__(self):

print “Base init”

則普通方法如下

class Leaf(Base):

def __init__(self):

Base.__init__(self)

print “Leaf init”

super()方法如下

class Leaf(Base):

def __init__(self):

super(Leaf, self).__init__()

print “Leaf init”

兩種方法,效果一緻:

>>> leaf = Leaf()

Base init

Leaf init

2.使用普通方法的缺陷

使用普通方法遇到鑽石繼承時,會遇到難題(孫子輩子類繼承自兩個叔叔級的父類,爺爺輩的類會初始化兩次)。

python中super的了解_super的了解和使用【轉載】

如果使用普通方法時,Leaf 類同時調用Medium1,Medium2時會初始化兩次Base類。

class Base(object):

def __init__(self):

print “Base init”

class Medium1(Base):

def __init__(self):

Base.__init__(self)

print “Medium1 init”

class Medium2(Base):

def __init__(self):

Base.__init__(self)

print “Medium2 init”

class Leaf(Medium1, Medium2):

def __init__(self):

Medium1.__init__(self)

Medium2.__init__(self)

print “Leaf init”

當生成Leaf對象時,Base 會被初始化兩次,結果如下:

>>> leaf = Leaf()

Base init

Medium1 init

Base init

Medium2 init

Leaf init

3.各語言的解決方法

鑽石繼承中,父類被多次初始化是個非常難纏的問題,我們來看看其他各個語言是如何解決這個問題的:

3.1. C++

C++使用虛拟繼承來解決鑽石繼承問題。

Medium1和Medium2虛拟繼承Base。當生成Leaf對象時,Medium1和Medium2并不會自動調用虛拟基類Base的構造函數,而需要由Leaf的構造函數顯式調用Base的構造函數。

3.2. Java

Java禁止使用多繼承。

Java使用單繼承+接口實作的方式來替代多繼承,避免了鑽石繼承産生的各種問題。

3.3. Ruby

Ruby禁止使用多繼承。

Ruby和Java一樣隻支援單繼承,但它對多繼承的替代方式和Java不同。Ruby使用Mixin的方式來替代,在目前類中mixin入其他子產品,來做到代碼的組裝效果。

3.4. Python

Python和C++一樣,支援多繼承的文法。但Python的解決思路和C++完全不一樣,Python是的用就是super

同樣的繼承 用super 重寫 ,生成Leaf對象試一下。

>>> leaf = Leaf()

Base init

Medium2 init

Medium1 init

Leaf init

相比于普通寫法,super完美解決鑽石繼承問題,并且代碼更為簡潔。

4. super的核心:mro

那super是如何解決磚石繼承問題的呢?要了解super的原理,就要先了解mro。mro是method resolution order的縮寫,表示了類繼承體系中的成員解析順序。

在python中,每個類都有一個mro的類方法。我們來看一下鑽石繼承中,Leaf類的mro是什麼樣子的:

>>> Leaf.mro()

[, , , , ]

可以看到mro方法傳回的是一個祖先類的清單。Leaf的每個祖先都在其中出現一次,這也是super在父類中查找成員的順序。

通過mro,python巧妙地将多繼承的圖結構,轉變為list的順序結構。super在繼承體系中向上的查找過程,變成了在mro中向右的線性查找過程,任何類都隻會被處理一次。

通過這個方法,python解決了多繼承中的2大難題:

查找順序問題。從Leaf的mro順序可以看出,如果Leaf類通過super來通路父類成員,那麼Medium1的成員會在Medium2之前被首先通路到。如果Medium1和Medium2都沒有找到,最後再到Base中查找。

鑽石繼承的多次初始化問題。在mro的list中,Base類隻出現了一次。事實上任何類都隻會在mro list中出現一次。這就確定了super向上調用的過程中,任何祖先類的方法都隻會被執行一次。

至于mro的生成算法,可以參考這篇wiki:C3 linearization

5. super的具體用法

我們首先來看一下python中的super定義

def __init__(self, type1=None, type2=None): # known special case of super.__init__

"""

super() -> same as super(__class__, )

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)

# (copied from class doc)

"""

下面詳細解析super中的下面兩種用法

5.1. super(type, obj)

補充:super()等價于super(__class__, self)

當我們在Leaf的__init__中寫這樣的super時:

class Leaf(Medium1, Medium2):

def __init__(self):

super(Leaf, self).__init__()

print “Leaf init”

super(Leaf, self).__init__()的意思是說:

擷取self所屬類的mro, 也就是[Leaf, Medium1, Medium2, Base]

從mro中Leaf右邊的一個類開始,依次尋找__init__函數。這裡是從Medium1開始尋找

一旦找到,就把找到的__init__函數綁定到self對象,并傳回

從這個執行流程可以看到,如果我們不想調用Medium1的__init__,而想要調用Medium2的__init__,那麼super應該寫成:super(Medium1, self)__init__()

案例了解:

class Root(object):

def __init__(self):

print("this is Root")

class B(Root):

def __init__(self):

print("enter B")

# print(self)  # this will print <__main__.d object at>

super(B, self).__init__()

print("leave B")

class C(Root):

def __init__(self):

print("enter C")

super(C, self).__init__()

print("leave C")

class D(B, C):

pass

d = D()

print(d.__class__.__mro__)

輸出

enter B

enter C

this is Root

leave C

leave B

(, , , , )

為什麼 enter B 下一句是 enter C 而不是 this is Root(如果認為 super 代表“調用父類的方法”,會想當然的認為下一句應該是this is Root)?。流程如下,在 B 的 __init__ 函數中:

super(B, self).__init__()

首先,我們擷取 self.__class__.__mro__,注意這裡的 self 是 D 的 instance 而不是 B 的

(, , , , )

然後,通過 B 來定位 MRO 中的 index,并找到下一個。顯然 B 的下一個是 C。于是,我們調用 C 的 __init__,打出 enter C。

5.2. super(type, type2)

在Base,Medium1,Medium2, Leaf類中分别寫入__new__()方法,并使用super():

class Base(object):

def __new__(self):

print "Base new"

class Medium1(Base):

def __new__(cls):

super(Medium1,cls).__new__(cls)

print "Medium1 new"

class Medium2(Base):

def __new__(cls):

super(Medium2,cls).__new__(cls)

print "Medium2 new"

class Leaf(Medium1, Medium2):

def __new__(cls):

obj = super(Leaf, cls).__new__(cls)

print "Leaf new"

return obj

super(Leaf, cls).__new__(cls)的意思是說:

擷取cls這個類的mro,這裡也是[Leaf, Medium1, Medium2, Base]

從mro中Leaf右邊的一個類開始,依次尋找__new__函數

一旦找到,就傳回“非綁定”的__new__函數

由于傳回的是非綁定的函數對象,是以調用時不能省略函數的第一個參數。這也是這裡調用__new__時,需要傳入參數cls的原因

同樣的,如果我們想從某個mro的某個位置開始查找,隻需要修改super的第一個參數就行。

一個很好的應用就是單例模式:

class Singleton(object):

def __new__(cls, *args, **kwargs):

if not hasattr(cls,'_instance'):

cls._instance = super(Singleton,cls).__new__(cls,*args,**kwargs)

return cls._instance

singleton1 = Singleton()

singleton2 = Singleton()

print(singleton1 is singleton2)

補充:Python3按照廣度優先

class C:

def f(self):

print('C')

super(C, self).f()

class A(C):

def f(self):

print('A')

super(A, self).f()

class B:

def f(self):

print('B')

class Foo(A,B):

pass

foo = Foo()

foo.f()

輸出

A

C

B