python 多态和 super 用法

python 中的多态实现非常简单,只要是在子类中实现和父类同名的方法,便能实现多态,如果想在子类中调用父类的方法,有多种方法,但是当涉及菱形继承等问题是,super 就成为了比较好的解决方案。

普通继承

对于比较简单的继承关系,通常在子类中有两种方法来执行父类的方法,示例如下。

基类:

1
2
3
class Base(object):
    def __init__(self):
        print("init Base")

示例 1:

1
2
3
4
5
6
7
class A(Base):
    def __init__(self):
        # 通过父类显式执行
        Base.__init__(self)
        print("init A")

a = A()

输出:

1
2
init Base
init A

示例 2:

1
2
3
4
5
6
7
class B(Base):
    def __init__(self):
        # 调用 super
        super(B, self).__init__()
        print("init B")

b = B()

输出:

1
2
init Base
init B

可以看到,两种方法都可以调用父类的方法对父类进行初始化。需要注意的是,两种方法都要传入 self,但是在子类中调用父类的 super 中传入的 self是子类对象。

菱形继承

当有多重继承,特别是菱形继承时,这两种方法就有区别了,示例如下。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base(object):
    def __init__(self):
        print("init Base")
        
class A(Base):
    def __init__(self):
        Base.__init__(self)
        print("init A")

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print("init B")
        
class C(A, B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print("init C")
c = C()

输出:

1
2
3
4
5
init Base
init A
init Base
init B
init C

可以看到,Base 被 init 了两次,至于其缺陷,在 C++ 中就已经讨论过了,反正就是不符合我们的预期,不想这种实现。C++ 中通过虚继承解决菱形继承问题,在 python 中可以使用 super 规避这种缺陷。

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base(object):
    def __init__(self):
        print("init Base")
        
class A(Base):
    def __init__(self):
        super(A, self).__init__()
        print("init A")

class B(Base):
    def __init__(self):
        super(B, self).__init__()
        print("init B")
        
class C(A, B):
    def __init__(self):
        super(C, self).__init__()
        print("init C")
c = C()

输出

1
2
3
4
init Base
init B
init A
init C

运行这个新版本后,你会发现每个 __init__() 方法只会被调用一次了。

为了弄清它的原理,我们需要花点时间解释下 python 是如何实现继承的。对于你定义的每一个类,python 会计算出一个所谓的方法解析顺序(MRO)列表。 这个 MRO 列表就是一个简单的所有基类的线性顺序表。我们可以看一下 C 的 MRO 表。

1
2
>>> C.__mro__
(__main__.C, __main__.A, __main__.B, __main__.Base, object)

为了实现继承,python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个 MRO 列表的构造是通过一个 C3 线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

必须牢记:MRO 列表中的类顺序会让你定义的任意类层级关系变得有意义。

当使用 super() 函数时,python 会在 MRO 列表上继续搜索下一个类(这是一种嵌套实现)。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个 MRO 列表,每个方法也只会被调用一次。 这也是为什么在第二个例子中你不会调用两次 Base.__init__() 的原因。换句话说,super 调用了次且仅有一次所有的父类。

由于 super 递归调用的会继续搜索的特性,可能会出现一些意向不到的效果,比如下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A(object):
    def spam(self):
        print('A.spam')
        super(A, self).spam()

class B(object):
    def spam(self):
        print('B.spam')

class C(A, B):
    pass

c = C()
c.spam()

C.__mro__

输出

1
2
3
A.spam
B.spam
(__main__.C, __main__.A, __main__.B, object)

为啥 c.spam() 会同时调用 A 和 B 的 spam()?其实看到 MRO 顺序就明白了:

  • 首先在 c 的类 C 中查找 spam 方法,没有找到就查找 A 中的 spam 方法。
  • 调用 A 中的 spam 方法,然后遇到 A 的 super 调用,继续在 MRO 顺序表中查找 spam 方法。注意,这里本来调用了 A 的 spam 就应该返回的,但是 super 的存在,导致了继续递归。
  • 遇到 B 的 spam 方法,调用,结束。

super 的使用

对于 python2 和 python3,super 的用法有一些区别:

原因:

  • python2 没有默认继承 object
  • python3 默认全部继承 object 类,都是新式类

用法区别:

  • python2: super(开始类名,self).函数名()
  • python3:super().函数名()
参考资料
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python是一种面向对象的编程语言,支持封装、继承和多等面向对象编程的特性。多是面向对象编程中的一种基本概念,是指同一种行为具有多种不同的表现形式或形。 在Python中,多可以通过方法的重写和多来实现。方法的重写是指子类重写父类中的方法,从而实现多。例如: ```python class Animal: def __init__(self, name): self._name = name def speak(self): print(f"{self._name} is speaking.") class Dog(Animal): def __init__(self, name): super().__init__(name) def speak(self): print(f"{self._name} is barking.") class Cat(Animal): def __init__(self, name): super().__init__(name) def speak(self): print(f"{self._name} is meowing.") animals = [Dog("Tom"), Cat("Jerry")] for animal in animals: animal.speak() ``` 在上面的示例中,Animal是一个基类,Dog和Cat是两个子类。Dog和Cat都重写了Animal类中的speak方法,实现了自己的功能。当遍历animals数组并调用每个元素的speak方法时,会分别输出"Tom is barking."和"Jerry is meowing."。 多可以使代码更加灵活和易于扩展。例如,如果需要增加一种新的动物,只需要定义一个新的子类,并重写父类中的speak方法即可。同时,代码中不需要知道具体的实现细节,只需要知道每个动物都有一个speak方法即可,从而使代码更加简洁和易于维护。 在Python中,还可以使用抽象基类(ABC)来实现多。抽象基类是一个特殊的类,不能被实例化,只能被继承。通过定义一个抽象基类,并定义一个抽象方法,可以强制子类实现这个抽象方法,从而保证多的实现。例如: ```python from abc import ABC, abstractmethod class Animal(ABC): def __init__(self, name): self._name = name @abstractmethod def speak(self): pass class Dog(Animal): def __init__(self, name): super().__init__(name) def speak(self): print(f"{self._name} is barking.") class Cat(Animal): def __init__(self, name): super().__init__(name) def speak(self): print(f"{self._name} is meowing.") animals = [Dog("Tom"), Cat("Jerry")] for animal in animals: animal.speak() ``` 在上面的示例中,Animal是一个抽象基类,定义了一个抽象方法speak。Dog和Cat都继承了Animal类,并实现了speak方法。这样,通过将Animal类定义为抽象基类,并定义speak方法为抽象方法,可以强制子类实现这个方法,从而保证多的实现。 总之,多是面向对象编程中的一种基本概念,可以使同一种行为具有多种不同的表现形式或形。在Python中,多可以通过方法的重写和多来实现。同时,抽象基类也可以用来实现多,从而强制子类实现抽象方法,保证多的实现。多可以使代码更加灵活和易于扩展,从而提高代码的可靠性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值