这篇文章将为大家详细讲解有关python中super()函数怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
看下面一个例子:
class A: def func(self): print("A的func执行") class B(A): def func(self): super().func() print("B扩展的func执行") b = B() b.func() # 输出结果为: # A的func执行 # B扩展的func执行
上面程序中,A是父类,B是A的子类,我们在A类中重定义了func()方法,在B类中重新定义了func()方法,在方法中通过super().func()又调用了父类的方法,所以执行结果才会有A类func()方法输出。
如果经常看Python内置库及第三方库源码的话,你会发现,super用的非常多的地方是在子类中调用父类的初始化__init__()方法,这种用法非常常见。
class A: def __init__(self, x): self.x = x class B(A): def __init__(self, x, y): super().__init__(x) self.y = y b = B(1, 2) print(b.x, b.y)
看到这,你会想到super就是用来获取父类并用来调用父类方法的,这样说对不对呢,其实是不对的,使用supper获取的不是父类,而是MRO列表中的下一个类,所谓MRO列表即方法解析顺序(Method Resolution Order)列表,它代表着类继承的顺序,我们可以使用以下几种获得某个类的MRO列表:
C.mro() C.__mro__ c.__class__.__mro__
MRO列表的顺序确定经历了很多次的变迁,最新的是通过C3线性化算法来实现的,感兴趣的话可以自行了解一下,总的来说,一个类的MRO列表就是合并所有父类的MRO列表,并遵循以下三条原则:
子类永远在父类前面
如果有多个父类,会根据它们在列表中的顺序被检查
如果对下一个类存在两个合法的选择,选择第一个父类
下面来看一下下面这个例子:
class A(Base): def func(self): print("A的func执行") super().func() print("A的func执行完毕") class B(Base): def func(self): print("B的func执行") super().func() print("B的func执行完毕") class C(A, B): def func(self): print("C的func执行") super().func() print("C的func执行完毕") c = C() c.func() # 获取MRO列表 print(c.__class__.__mro__)
执行结果如下:
上述程序中,Base是父类,A、B都继承自Base,C继承自 A、B,它们的继承关系就是一个典型的菱形继承,如下:
通过结果我们可以看出,super并不是获取父类并用来调用父类的方法,而是根据MRO列表一次调用下一个类,使用c.__class__.__mro__可以获取MRO列表,MRO列表的顺序是C、A、B、Base、object。
super计算方法解析顺序中的下一个类,可以接收两个参数:
def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1]
通过inst负责生成MRO列表
通过cls定位在MRO列表中的index, 并返回mro[index + 1]
Python 中,由于基类不会在 __init__() 中被隐式地调用,需要程序员显式调用它们。这种情况下,当程序中包含多重继承的类层次结构时,使用 super 是非常危险的,往往会在类的初始化过程中出现问题。
分析如下程序,C 类使用了 __init__() 方法调用它的基类,会造成 B 类被调用了 2 次:
class A: def __init__(self): print("A",end=" ") super().__init__() class B: def __init__(self): print("B",end=" ") super().__init__() class C(A,B): def __init__(self): print("C",end=" ") A.__init__(self) B.__init__(self) print("MRO:",[x.__name__ for x in C.__mro__]) C()
运行结果为:
MRO: ['C', 'A', 'B', 'object']
C A B B
出现以上这种情况的原因在于,C 的实例调用 A.__init__(self),使得 super(A,self).__init__() 调用了 B.__init__() 方法。换句话说,super 应该被用到整个类的层次结构中。
但是,有时这种层次结构的一部分位于第三方代码中,我们无法确定外部包的这些代码中是否使用 super(),因此,当需要对某个第三方类进行子类化时,最好查看其内部代码以及 MRO 中其他类的内部代码。
使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名,一个类怎么能调用其基类的 __init__() 代码呢?这会导致下列问题:
class commonBase: def __init__(self): print("commonBase") super().__init__() class base1(commonBase): def __init__(self): print("base1") super().__init__() class base2(commonBase): def __init__(self): print("base2") super().__init__() class myClass(base1,base2): def __init__(self,arg): print("my base") super().__init__(arg) myClass(10)
运行结果为:
my base
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\demo.py", line 20, in <module>
myClass(10)
File "C:\Users\mengma\Desktop\demo.py", line 19, in __init__
super().__init__(arg)
TypeError: __init__() takes 1 positional argument but 2 were given
一种解决方法是使用 *args 和 **kwargs 包装的参数和关键字参数,这样即使不使用它们,所有的构造函数也会传递所有参数,如下所示:
class commonBase: def __init__(self,*args,**kwargs): print("commonBase") super().__init__() class base1(commonBase): def __init__(self,*args,**kwargs): print("base1") super().__init__(*args,**kwargs) class base2(commonBase): def __init__(self,*args,**kwargs): print("base2") super().__init__(*args,**kwargs) class myClass(base1,base2): def __init__(self,arg): print("my base") super().__init__(arg) myClass(10)
运行结果为:
my base
base1
base2
commonBase
不过,这是一种很糟糕的解决方法,由于任何参数都可以传入,所有构造函数都可以接受任何类型的参数,这会导致代码变得脆弱。另一种解决方法是在 MyClass 中显式地使用特定类的 __init__() 调用,但这无疑会导致第一种错误。
关于“python中super()函数怎么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。