如非特别说明,下文均基于 Python3
摘要
本文讲述
继承关系中如何通过
- Python
调用 "父类" 方法,
- super()
返回
- super(Type, CurrentClass)
的
- CurrentClass
中
- MRO
的下一个类的代理;以及如何设计
- Type
类以便正确初始化。
- Python
在继承中,调用父类方法是很有必要的。调用父类方法的场景有很多:
才能正确初始化父类实例属性,使得子类实例对象能够继承到父类实例对象的实例属性;
- __init__
单继承是最简单的继承关系,多继承过于复杂,而且使用起来容易出错。因此一些高级语言完全摒弃了多继承,只支持单继承;一些高级语言虽然支持多继承,但也不推荐使用多继承。
也是一样,在不能完全掌握多继承时,最好不好使用,单继承能满足绝大部分的需求。
- Python
绑定方法与非绑定方法的区别与联系参见:Python 基础 - 类
如有以下继承关系两个类:
- class D(object):
- def test(self):
- print('test in D')
- class C(D):
- def test(self):
- print('test in C')
- D.test(self)
现在要求在子类
的
- C
函数中调用父类
- test
的
- D
实现。我们能想到最直接的方法恐怕是直接引用类对象
- test
的函数成员
- D
了:
- test
- class D(object):
- def test(self):
- print('test in D')
- class C(D):
- def test(self):
- print('test in C')
尝试测试一下:
- c = C()
- c.test()
output:
- test in C
- test in D
看来非绑定的方式确实满足了当前调用父类方法的需求。
参考 Python tutorial 关于 super 的描述:
- 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 same as that used by getattr() except that the type itself is skipped.
函数返回委托类
- super
的父类或者兄弟类方法调用的代理对象。
- type
用来调用已经在子类中重写了的父类方法。方法的搜索顺序与
- super
函数相同,只是参数类
- getattr()
本身被忽略。
- type
使用绑定方式调用父类方法,自然不能显式传入参数当前对象 (
)。现在
- 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()的省略形式
事实上,
函数返回的代理对象是一个
- super
,正如它的名字所指,类
- bultin class super
代理了子类的父类。在单继承关系中,
- super
代理的类很容易找到吗,就是子类的唯一父类;但是在多继承关系中,
- super
除了能代理子类的父类外,还有可能代理子类的兄弟类。
- super
在多继承关系中,继承关系可能会相当复杂。
- 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
类
继承层次结构如下:
- A
- object
- |
- D
- / \
- B C
- \ /
- A
类
的继承关系中存在菱形结构,即可以通过多条路径从类
- A
到达某个父类,这里是
- A
。
- D
如果现在要求在类
中调用 " 父类 " 的
- A
方法,需要一种对
- test
方法的搜索解析顺序,来决定到底是调用
- test
的
- B,C或D
方法。
- test
上面提出的对
的方法的搜索顺序,就是方法解析顺序了。
- test
深度优先
旧式类中,方法解析顺序是深度优先,多个父类从左到右。 广度优先
- Python
新式类中,方法解析顺序是广度优先,多个父类从左到右。
- Python
所以上面的解析顺序是:
。
- A -> B -> C -> D -> object
中,类的
- Python
属性展示了方法搜索顺序,可以调用
- __mro__
方法或者直接引用
- mro()
得到搜索顺序:
- __mro__
- 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
类
的 MRO:
- B
对比类
- B -> D -> object
的 MRO:
- A
同样的类
- A -> B -> C -> D -> object
,在两个不同的 MRO 中位置关系也是不同的。可以说,在已有的继承关系中加入新的子类,会在 MRO 中引入新的类,并且改变解析顺序。
- B
那么可以想象,同样在类
的 test 中通过
- B
调用父类方法,在不同的 MRO 中实际调用的方法是不同的。
- super
如下:
- 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):
- pass
- b = 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
因此在设计多继承关系的类体系时,要特别注意这一点。
方法
,返回的是对
- 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 class
- return 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()代理D
- print('==========')
- 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)
这两种方式得到的结果是相同的,区别在于非绑定调用与绑定调用。
普通的函数或者方法调用中,调用者肯定事先知道被调用者所需的参数,然后可以轻松的组织参数调用。但是在多继承关系中,情况有些尴尬,使用
代理调用方法,编写类的作者并不知道最终会调用哪个类的方法,这个类都可能尚未存在。
- 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):
- pass
- a = 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):
- pass
- a = A()
- a.test()
类
的调用方式调用类
- B
的
- C
方法肯定会失败,因为没有提供任何参数。类
- test
的作者是不可能去修改类
- C
的实现。那么,如何适应这种参数变换的需求,是在设计
- B
类中需要考虑的问题。
- Python
事实上,这种参数的变换在构造方法上能体现得淋漓尽致,如果子类没有正确初始化父类,那么子类甚至不能从父类继承到需要的实例属性。
所以,
的类必须设计友好,才能拓展,有以下三条指导原则:
- Python
调用的方法必须存在;
- super()
- super()
代理的类是不可预测的,需要匹配调用者和可能未知的调用者的参数。
- super()
固定参数
一种方法是使用位置参数固定函数签名。就像以上使用的
一样,其签名是固定的,只要要传递固定的参数,总是不会出错。
- test()
关键字参数
每个类的构造方法可能需要不同的参数,这时固定参数满足不了这种需求了。幸好,
中的关键字参数可以满足不定参数的需求。设计函数参数时,参数由关键字参数和关键字参数字典组成,在调用链中,每一个函数获取其所需的关键字参数,保留不需要的参数到
- Python
中,传递到调用链的下一个函数,最终
- **kwargs
为空时,调用调用链中的最后一个函数。
- **kwargs
示例:
- class Shape(object):
- def __init__(self, shapename, **kwargs):
- self.shapename = shapename
- super().__init__(**kwargs)
- class ColoredShape(Shape):
- def __init__(self, color, **kwargs):
- self.color = color
- super().__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()
上面的例子中,由于顶层父类
总是存在
- object
方法,在任何
- __init__
链中也总是最后一个,因此任意的
- MRO
调用总能保证是
- super().__init__
结束。
- object.__init__
但是其他自定义的方法得不到这样的保证。这时需要手动创建类似
的顶层父类:
- object
- class Root:
- def draw(self):
- # the delegation chain stops here
- assert not hasattr(super(), 'draw')
- class Shape(Root):
- def __init__(self, shapename, **kwds):
- self.shapename = shapename
- super().__init__(**kwds)
- def draw(self):
- print('Drawing. Setting shape to:', self.shapename)
- super().draw()
- class ColoredShape(Shape):
- def __init__(self, color, **kwds):
- self.color = color
- super().__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
对于那些不友好的类:
- class Moveable:
- def __init__(self, x, y):
- self.x = x
- self.y = y
- def 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):
- pass
- MovableColoredShape(color='red', shapename='triangle',
- x=10, y=20).draw()
Python's super() considered super!
Python tutorial#super
来源: http://www.cnblogs.com/crazyrunning/p/7095014.html