Detailed explanation of MRO and super in inheritance
Python Advanced - MRO and Super in Inheritance
is written in front
Unless otherwise specified, the following is based on Python3
Abstract
This article describes how to Python
call the "parent class" method through super()
in the inheritance relationship, super(Type, CurrentClass)
Return the proxy for the next class in Type
in CurrentClass
's MRO
; and how to design the Python
class so that it is initialized correctly.
1. Calling parent class methods in single inheritance
In inheritance, it is necessary to call parent class methods. There are many scenarios for calling parent class methods:
For example, the constructor method of the parent class must be called
__init__
to correctly initialize the parent class instance attributes so that the subclass instance object can Inherited from the instance attributes of the parent class instance object;Another example is when you need to override the parent class method. Sometimes it is not necessary to completely abandon the parent class implementation, but just add some before and after the parent class implementation. To implement, you still have to call the parent class method
Single inheritance is the simplest inheritance relationship. Multiple inheritance is too complicated and error-prone to use. Therefore, some high-level languages completely abandon multiple inheritance and only support single inheritance; although some high-level languages support multiple inheritance, multiple inheritance is not recommended. Python
The same is true. When you cannot fully master multiple inheritance, it is best not to use it. Single inheritance can meet most needs.
1.1 Calling in non-binding mode
Differences and connections between bound methods and non-binding methods, please refer to: Python Basics - Classes
If there are two classes with the following inheritance relationship :
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C') D.test(self)
Now requires calling the test# of the parent class
D in the
test function of the subclass
C ##accomplish. The most direct method we can think of is probably to directly reference the function member
test of the class object
D:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')
c = C() c.test()
test in C test in D
super(\[type\[, object-or-type\]\])
Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is the same as that used by getattr( ) except that the type itself is skipped.
superThe function returns the proxy object called by the parent class or sibling class method of the delegate class
type.
super is used to call the parent class method that has been overridden in the subclass. The search order for methods is the same as the
getattr() function, except that the parameter class
type itself is ignored.
self) cannot be passed in explicitly. Now the
super function can scope the proxy of the parent class, because in single inheritance the subclass has and has only one parent class, so the parent class is clear, and we completely know which parent class method is called:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')super().test() # super(C, self).test()的省略形式
super function is a
bultin class super, As its name implies, class
super represents the parent class of the subclass. In a single inheritance relationship,
super is easy to find the proxy class, which is the only parent class of the subclass; but in a multiple inheritance relationship,
super can not only proxy the parent class of the subclass Outside the class, it is possible to proxy sibling classes of subclasses.
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')class A(B, C):pass
AThe inheritance hierarchy is as follows:
object | D / \ B C \ / A
A, that is, it can be passed There are multiple paths from class
A to a certain parent class, in this case
D.
test method of "
parent class" in class A, you need a way to# The search and parsing order of the ##test
method determines whether to call the test
method of B, C or D
. 2.2 Method parsing order (MRO)
The search order for the methods of
test proposed above is the method parsing order.
Python
In old-style classes, the order of method resolution is depth first, and multiple parent classes are from left to right. Breadth first
Python
In new-style classes, the order of method resolution is breadth first, and multiple parent classes are from left to right. So the above parsing order is:
. In
, the __mro__
attribute of a class shows the method search order. You can call the mro()
method or directly reference __mro__
Get the search order:
print(A.mro())print(A.__mro__)
output:
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>] (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
所以
a = A() a.test() # output: test in B
变化的MRO
即使是同一个类,在不同的MRO中位置的前后关系都是不同的。如以下类:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')
类B
的继承层次结构为:
object | D / \ C B
类B
的MRO:B -> D -> object
对比类A
的MRO:A -> B -> C -> D -> object
同样的类B
,在两个不同的MRO中位置关系也是不同的。可以说,在已有的继承关系中加入新的子类,会在MRO中引入新的类,并且改变解析顺序。
那么可以想象,同样在类B
的test中通过super
调用父类方法,在不同的MRO中实际调用的方法是不同的。
如下:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')super().test()class B(D): def test(self):print('test in B')super().test()class A(B, C):passb = B() b.test()print('==========') a = A() a.test()
output:
test in B test in D==========test in B test in C test in D
因为在原有的类关系中加入B
和C
的子类A
,使得在B
的test
方法中调用super
的test
方法发生了改变,原来调用的是其父类D
的test
方法,现在调用的是其兄弟类C
的test
方法。
从这里可以看出super
不总是代理子类的父类,还有可能代理其兄弟类。
因此在设计多继承关系的类体系时,要特别注意这一点。
2.3 再看super方法
方法super([type[, object-or-type]])
,返回的是对type
的父类或兄弟类的代理。
如果第二个参数省略,返回的super
对象是未绑定到确定的MRO
上的:
如果第二个参数是对象,那么
isinstance(obj, type)
必须为True
;如果第二个参数是类型,那么
issubclass(type2, type)
必须为True
,即第二个参数类型是第一个参数类型的子类。
在super
函数的第二个参数存在时,其实现大概如以下:
def super(cls, inst): mro = inst.__class__.mro() # Always the most derived classreturn mro[mro.index(cls) + 1]
很明显,super
返回在第二个参数对应类的MRO
列表中,第一个参数type
的下一个类的代理。因此,要求第一个参数type
存在于第二个参数类的MRO
是必要的,只有第一个参数类是第二个参数所对应类的父类,才能保证。
super()
super
函数是要求有参数的,不存在无参的super
函数。在类定义中以super()
方式调用,是一种省略写法,由解释器填充必要参数。填充的第一个参数是当前类,第二个参数是self
:
super() => super(current_class, self)
所以,super()
这种写法注定只能在类定义中使用。
现在再来看上面的继承关系:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')# super().test() # 与下面的写法等价super(C, self).test() # 返回self对应类的MRO中,类C的下一个类的代理class B(D):def test(self):print('test in B')# super().test() # 与下面的写法等价super(B, self).test() # 返回self对应类的MRO中,类B的下一个类的代理class A(B, C):pass
因此:
b = B() b.test() # 基于类B的MRO(B->D->object),类B中的super()代理Dprint('==========') a = A() a.test() # 基于类A的MRO(A->B->C->D->object),类B中的super()代理C
以上就是在继承关系中引入新类,改变方法解析顺序的实例。
super([type[, object-or-type]])
的第二个参数,对象和类还有一点区别:使用对象返回的是代理使用绑定方法,使用类返回的代理使用非绑定方法。
如:
b = B()super(B, b).test()super(B, B).test(b)
这两种方式得到的结果是相同的,区别在于非绑定调用与绑定调用。
3. 最佳实践
3.1 不可预测的调用
普通的函数或者方法调用中,调用者肯定事先知道被调用者所需的参数,然后可以轻松的组织参数调用。但是在多继承关系中,情况有些尴尬,使用super
代理调用方法,编写类的作者并不知道最终会调用哪个类的方法,这个类都可能尚未存在。
如现在一作者编写了以下类:
class D(object):def test(self):print('test in D') class B(D):def test(self):print('test in B')super().test()
在定义类D
时,作者完全不可能知道test
方法中的super().test()
最终会调用到哪个类。
因为如果后来有人在这个类体系的基础上,引入了如下类:
class C(D):def test(self):print('test in C')super().test() class A(B, C):passa = A() a.test()
此时会发现类B
的test
方法中super().test()
调用了非原作者编写的类的方法。
这里test
方法的参数都是确定的,但是在实际生产中,可能各个类的test
方法都是不同的,如果新引入的类C
需要不同的参数:
class C(D):def test(self, param_c):print('test in C, param is', param_c)super().test() class A(B, C):passa = A() a.test()
类B
的调用方式调用类C
的test
方法肯定会失败,因为没有提供任何参数。类C
的作者是不可能去修改类B
的实现。那么,如何适应这种参数变换的需求,是在设计Python
类中需要考虑的问题。
3.2 实践建议
事实上,这种参数的变换在构造方法上能体现得淋漓尽致,如果子类没有正确初始化父类,那么子类甚至不能从父类继承到需要的实例属性。
所以,Python
的类必须设计友好,才能拓展,有以下三条指导原则:
通过
super()
调用的方法必须存在;调用者和被调用者参数必须匹配;
所有对父类方法的调用都必须使用
super()
3.3 参数匹配
super()
代理的类是不可预测的,需要匹配调用者和可能未知的调用者的参数。
固定参数
一种方法是使用位置参数固定函数签名。就像以上使用的test()
一样,其签名是固定的,只要要传递固定的参数,总是不会出错。
关键字参数
每个类的构造方法可能需要不同的参数,这时固定参数满足不了这种需求了。幸好,Python
中的关键字参数可以满足不定参数的需求。设计函数参数时,参数由关键字参数和关键字参数字典组成,在调用链中,每一个函数获取其所需的关键字参数,保留不需要的参数到**kwargs
中,传递到调用链的下一个函数,最终**kwargs
为空时,调用调用链中的最后一个函数。
示例:
class Shape(object):def __init__(self, shapename, **kwargs):self.shapename = shapenamesuper().__init__(**kwargs)class ColoredShape(Shape):def __init__(self, color, **kwargs):self.color = colorsuper().__init__(**kwargs) cs = ColoredShape(color='red', shapename='circle')
参数的剥落步骤为:
使用
cs = ColoredShape(color='red', shapename='circle')
初始化ColoredShape
;ColoredShape
的__init__
方法获取其需要的关键字参数color
,此时的kwargs
为{shapename:'circle'}
;调用调用链中
Shape
的__init__
方法,该方法获取所需关键字参数shapename
,此时kwargs
为{}
;最后调用调用链末端
objet.__init__
,此时因为kwargs
已经为空。
初始化子类传递的关键字参数尤为重要,如果少传或多传,都会导致初始化不成功。只有MRO
中每个类的方法都是用super()
来调用“父类”方法时,才能保证super()
调用链不会断掉。
3.4 保证方法存在
上面的例子中,由于顶层父类object
总是存在__init__
方法,在任何MRO
链中也总是最后一个,因此任意的super().__init__
调用总能保证是object.__init__
结束。
但是其他自定义的方法得不到这样的保证。这时需要手动创建类似object
的顶层父类:
class Root:def draw(self):# the delegation chain stops hereassert not hasattr(super(), 'draw')class Shape(Root):def __init__(self, shapename, **kwds):self.shapename = shapenamesuper().__init__(**kwds)def draw(self):print('Drawing. Setting shape to:', self.shapename)super().draw()class ColoredShape(Shape):def __init__(self, color, **kwds):self.color = colorsuper().__init__(**kwds)def draw(self):print('Drawing. Setting color to:', self.color)super().draw() cs = ColoredShape(color='blue', shapename='square') cs.draw()
如果有新的类要加入到这个MRO
体系,新的子类也要继承Root
,这样,所有的对draw()
的调用都会经过Root
,而不会到达没有draw
方法的object
了。这种对于子类的扩展要求,应当详细注明在文档中,便于使用者阅读。这种限制与Python
所有异常都必须继承自BaseException
一样。
3.5 组合不友好的类
对于那些不友好的类:
class Moveable:def __init__(self, x, y):self.x = xself.y = ydef draw(self):print('Drawing at position:', self.x, self.y)
如果希望使用它的功能,直接将其加入到我们友好的继承体系中,会破坏原有类的友好性。
除了通过继承获得第三方功能外,还有一种称之为组合的方式,即把第三方类作为组件的方式揉入类中,使得类具有第三方的功能:
class MoveableAdapter(Root):def __init__(self, x, y, **kwds):self.movable = Moveable(x, y)super().__init__(**kwds)def draw(self):self.movable.draw()super().draw()
Moveable
被作为组件整合到适配类MoveableAdapter
中,适配类拥有了Moveable
的功能,而且是友好实现的。完全可以通过继承适配类的方式,将Moveable
的功能加入到友好的继承体系中:
class MovableColoredShape(ColoredShape, MoveableAdapter):passMovableColoredShape(color='red', shapename='triangle', x=10, y=20).draw()
参考
Python’s super() considered super!
Python tutorial#super
The above is the detailed content of Detailed explanation of MRO and super in inheritance. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



VS Code can be used to write Python and provides many features that make it an ideal tool for developing Python applications. It allows users to: install Python extensions to get functions such as code completion, syntax highlighting, and debugging. Use the debugger to track code step by step, find and fix errors. Integrate Git for version control. Use code formatting tools to maintain code consistency. Use the Linting tool to spot potential problems ahead of time.

In VS Code, you can run the program in the terminal through the following steps: Prepare the code and open the integrated terminal to ensure that the code directory is consistent with the terminal working directory. Select the run command according to the programming language (such as Python's python your_file_name.py) to check whether it runs successfully and resolve errors. Use the debugger to improve debugging efficiency.

VS Code can run on Windows 8, but the experience may not be great. First make sure the system has been updated to the latest patch, then download the VS Code installation package that matches the system architecture and install it as prompted. After installation, be aware that some extensions may be incompatible with Windows 8 and need to look for alternative extensions or use newer Windows systems in a virtual machine. Install the necessary extensions to check whether they work properly. Although VS Code is feasible on Windows 8, it is recommended to upgrade to a newer Windows system for a better development experience and security.

VS Code extensions pose malicious risks, such as hiding malicious code, exploiting vulnerabilities, and masturbating as legitimate extensions. Methods to identify malicious extensions include: checking publishers, reading comments, checking code, and installing with caution. Security measures also include: security awareness, good habits, regular updates and antivirus software.

VS Code is the full name Visual Studio Code, which is a free and open source cross-platform code editor and development environment developed by Microsoft. It supports a wide range of programming languages and provides syntax highlighting, code automatic completion, code snippets and smart prompts to improve development efficiency. Through a rich extension ecosystem, users can add extensions to specific needs and languages, such as debuggers, code formatting tools, and Git integrations. VS Code also includes an intuitive debugger that helps quickly find and resolve bugs in your code.

Python excels in automation, scripting, and task management. 1) Automation: File backup is realized through standard libraries such as os and shutil. 2) Script writing: Use the psutil library to monitor system resources. 3) Task management: Use the schedule library to schedule tasks. Python's ease of use and rich library support makes it the preferred tool in these areas.

VS Code not only can run Python, but also provides powerful functions, including: automatically identifying Python files after installing Python extensions, providing functions such as code completion, syntax highlighting, and debugging. Relying on the installed Python environment, extensions act as bridge connection editing and Python environment. The debugging functions include setting breakpoints, step-by-step debugging, viewing variable values, and improving debugging efficiency. The integrated terminal supports running complex commands such as unit testing and package management. Supports extended configuration and enhances features such as code formatting, analysis and version control.

Yes, VS Code can run Python code. To run Python efficiently in VS Code, complete the following steps: Install the Python interpreter and configure environment variables. Install the Python extension in VS Code. Run Python code in VS Code's terminal via the command line. Use VS Code's debugging capabilities and code formatting to improve development efficiency. Adopt good programming habits and use performance analysis tools to optimize code performance.
