Python的super解释
Python的super解释
shawnplaying 发表于1年前
Python的super解释
  • 发表于 1年前
  • 阅读 8
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

在子类中初始化父类, 传统的方式是在子类中直接调用父类的__init__函数:

class MyBaseClass(object):
    def __init__(self, value):
        self.value = value

class MyChildClass(MyBaseClass):
    def __init__(self):
        MyBaseClass.__init__(self, 5)

在继承体系很简单的情况下, 这种方式可以正常运行, 但是在大多数情况下这种方法都是不可行的. 如果一个类继承自多个类, 那么直接调用父类的__init__函数就会产生不可预知的结果.

问题之一是, 在子类里调用__init__的顺序并不固定. 例如定义两个父类:

class TimesTwo(object):
    def __init__(self):
        self.value *= 2

class PlusFive(object):
    def __init__(self):
        self.value += 5

下面这个类以其中一种顺序继承上述两个类:

class OneWay(MyBaseClass, TimesTwo, PlusFive):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        TimesTwo.__init__(self)
        PlusFive.__init__(self)

构建一个实例, 其产生的结果与继承的顺序是一致的:

foo = OneWay(5)
print('First ordering is (5 * 2) + 5 =', foo.value)

>>>
First ordering is (5 * 2) + 5 = 15

下面这个类以另外一种顺序继承两个父类:

class AnotherWay(MyBaseClass, PlusFive, TimesTwo):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        TimesTwo.__init__(self)
        PlusFive.__init__(self)

但是在构造函数中调用父类__init__方法的顺序没有改变, 这就导致了该类所产生的结果与其继承顺序不一致:

bar = AnotherWay(5)
print('Second ordering still is', bar.value)

>>>
Second ordering still is 15

另一种问题出现在菱形继承(diamond inheritance)中. 菱形继承是指一个类继承自两个不同的类, 而这两个类有一个共同父类. 在菱形继承中, 公共父类的__init__构造方法会运行多次, 从而导致意料之外的结果. 例如, 以下两个类都继承自MyBaseClass:

class TimesFive(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value *= 5

class PlusTwo(MyBaseClass):
    def __init__(self, value):
        MyBaseClass.__init__(self, value)
        self.value += 2

以下这个类继承自上述两个类, 这就使得MyBaseClass成了菱形继承中的顶端类:

class ThisWay(TimesFive, PlusTwo):
    def __init__(self, value):
        TimesFive.__init__(self, value)
        PlusTwo.__init__(self, value)

我们来实例化这个类看一下效果:

foo = ThisWay(5)
print('Should be (5 * 5) + 2 = 27 but is', foo.value)
>>>
Should be (5 * 5) + 2 = 27 but is 7

我们本来想要的结果是(5 * 5) + 2 = 27, 但是调用第二个父类PlusTwo的构造方法PlusTwo.__init__时, 第二次调用了顶端父类MyBaseClass的构造方法MyBaseClass.__init__, 使得self.value又变成了5.

为了解决这个问题, Python2.2添加了一个名为super的内置函数, 并定义了方法解析顺序(Method Resolution Order, MRO). MRO以标准的流程来安排超类之间的初始化顺序, 也保证了菱形继承中顶端类的__init__方法只会运行一次.

以下代码使用Python2风格的super方法定义了菱形继承体系:

# Python2
class TimesFiveCorrect(MyBaseClass):
    def __init__(self, value):
        super(TimesFiveCorrect, self).__init__(value)
        self.value *= 5

class PlusTwoCorrect(MyBaseClass):
    def __init__(self, value):
        super(PlusTwoCorrect, self).__init__(value)
        self.value += 2

现在, 顶端类的构造方法MyBaseClass.__init__只会运行一次, 而其他超类的初始化顺序, 则与这些超类在class语句中的出现顺序相同:

# Python 2
class GoodWay(TimesFiveCorrect, PlusTwoCorrect):
    def __init__(self, value):
        super(GoodWay, self).__init__(value)

foo = GoodWay(5)
print 'Should be 5 * (5 + 2) = 35 and is', foo.value
>>>
Should be 5 * (5 + 2) = 35 and is 35

我们可能会认为应该是先*5, 然后+2得出结果是27才对. 但实际上, 程序的运行顺序和GoodWay类的MRO保持一致, 这个MRO顺序可以通过名为mro的方法来查询:

from pprint import pprint
pprint(GoodWay.mro())
>>>
[<class '__main__.GoodWay'>,
<class '__main__.TimesFiveCorrect'>,
<class '__main__.PlusTwoCorrect'>,
<class '__main__.MyBaseClass'>,
<class 'object'>]

调用GoodWay(5)时, 它会调用TimesFiveCorrect.__init__, 而TimesFiveCorrect.__init__又会调用PlusTwoCorrect.__init__, 接着PlusTwoCorrect.__init__调用MyBaseClass.__init__, 到达菱形顶部后, 所有的构造方法会按照与刚才那些__init__相反的顺序来执行, 于是MyBaseClass.__init__会把value设为5, 然后PlusTwoCorrect.__init__会为其加2, 变成7, 最后TimesFiveCorrect.__init__将其乘以5, 结果就变为35.

在Python2中有这样两个问题:

  1. super语句有些麻烦, 必须指定所在的类, self对象, 构造方法名(通常是__init__)以及所有的参数值.

  2. 调用super时, 必须写出当前类的名称, 但是类的名称很可能会更改, 那么每一条super语句也就必须要更改.

在Python3中则没有这些问题, 它提供了一种不带参数的super调用方式:

class Explicit(MyBaseClass):
    def __init__(self, value):
        super(__class__, self).__init__(value * 2)

class Implicit(MyBaseClass):
    def __init__(self, value):
        super().__init__(value * 2)

assert Explicit(10).value == Implicit(10).value

小结

  1. Python使用MRO来解决父类初始化顺序及菱形继承问题;

  2. 应该使用内置的super函数来初始化父类.

参考资料:

  1. Slatkin, Brett. Effective Python: 59 Specific Ways to Write Better Python. Pearson Education, 2015.

  2. Brett Slatkin, 爱飞翔. Effective Python: 编写高质量Python代码的59个有效 方法. 机械工业出版社, 2016.1.

共有 人打赏支持
粉丝 15
博文 125
码字总数 70640
×
shawnplaying
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: