重写超类方法

每个类都有一个或多个超类, 并从它们那里继承属性和方法. 对子类的实例调用方法(或访问其属性)时, 如果找不到该方法(或属性), 将在其超类中查找.

Example:

class A:
    def hello(self):
        print("Hello, I am A")

class B(A):
    pass

a = A()
b = B()
a.hello()
b.hello()

运行结果为:
Hello, I am A
Hello, I am A

由于在类B中没有定义方法hello, 因此对其调用方法hello时, 调用的是从类A继承的方法hello.

要在子类中添加功能, 一种基本方式是添加方法. 然而, 有时候我们可能想重写超类的某些方法, 以定制继承而来的行为.

重写的方法就是在子类中定义并实现同名的方法. 这样, 原来从超类中继承的同名方法就被”遮蔽”了, 调用该方法时, 默认调用的是重写后的方法.

例如, 接上面的例子, B可以重写方法hello:

class A:
    def hello(self):
        print("Hello, I am A")

class B(A):
    def hello(self):
        print("Hello, I am B")

a = A()
b = B()
a.hello()
b.hello()

运行结果为:
Hello, I am A
Hello, I am B

注意

Python并不支持函数的重载, 即使重写后的方法原型有所改变, 原来的同名方法也会别”遮蔽”.

重写是继承机制的一个重要方面, 对构造函数来说尤其如此. 构造函数用于初始化新建对象的状态,而对大多数子类来说, 除超类的初始化代码之外, 还需要有自己的初始化代码. 虽然所有方法的重写机制都相同, 但与重写普通方法相比, 重写构造函数时更有可能遇到一个特别问的问题: 重写构造函数时, 必须调用超类的构造函数, 否则可能无法正确地初始化对象.

Example:

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah...')
            self.hungry = False
        else:
            print('No, thanks!')

class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)

sb = SongBird()
sb.eat()

运行结果为:
Traceback (most recent call last):
    File "./a.py", line 21, in <module>
        sb.eat()
    File "./a.py", line 7, in eat
        if self.hungry:
AttributeError: 'SongBird' object has no attribute 'hungry'

SongBirdBird的子类, 继承了方法eat, 但如果尝试调用它, 将引发异常.

异常清楚地指出了问题出现但原因: SongBird没有属性hungry. 为何会这样呢? 因为在SongBird中重写了构造函数, 但新的构造函数没有包含任何初始化属性hungry的代码. 要消除这种错误, SongBird的构造函数中必须调用其超类的构造函数, 以确保基本的初始化得以执行.

调用超类方法

有两种方法来调用超类方法:

  • 调用未关联的超类方法
  • 使用super()函数(推荐的方法)

调用未关联的超类方法

通过实例调用方法时, 方法的参数self将自动关联到实例. 然而, 如果通过类调用方法, 就没有实例与其相关联. 在这种情况下, 便可设置参数self, 这样的方法称为未关联的.

  • 在子类的构造函数中调用超类的构造函数

Example:

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah...')
            self.hungry = False
        else:
            print('No, thanks!')

class SongBird(Bird):
    def __init__(self):
        # 调用超类的构造函数
        Bird.__init__(self)
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)
  • 在类外调用子类的超类方法

Example:

class A():
    def hello(self):
        print("Hello, I am A")

class B(A):
    def hello(self):
        print("Hello, I am B")

b = B()
# 调用超类的方法
A.hello(b)

运行结果为:
Hello, I am A

使用super()函数函数调用超类方法

  • 使用super()函数, 调用子类的超类方法

Example:

class A():
    def hello(self):
        print("Hello, I am A")

class B(A):
    def hello(self):
        print("Hello, I am B")

b = B()
# 使用super()函数调用超类方法
super(B, b).hello()

运行结果为:
Hello, I am A
  • 在类中不带任何参数调用super()函数

Exmaple:

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah...')
            self.hungry = False
        else:
            print('No, thanks!')

class SongBird(Bird):
    def __init__(self):
        # 使用super()函数, 调用超类的构造函数
        super().__init__()
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)

调用超类的构造方法

子类会继承超类所有的属性和方法, 父类的构造方法, 子类也同样会继承.

Python是一门支持多重继承的面向对象编程语言, 如果子类继承的多个超类中包含同名的方法, 则子类对象在调用该方法时, 会优先选择排在前面的超类中的该方法. 构造方法也是如此.

有的时候, 我们需要在子类中自定义构造方法, 则必须在该方法中调用超类的构造方法(尤其是多重继承时, 需要调用多个超类的构造方法).

在子类的构造方法中, 调用超类构造方法的方式有两种(和调用超类的普通方法一样), 分别是:

  • 调用未关联的超类方法;
  • 使用super()函数. 但如果涉及多重继承, 该函数只能调用第一个直接父类的构造方法.

注解

如果涉及到多重继承, 在子类的构造方法中, 调用第一个父类的构造方法的方式有以上两种, 而调用其它父类的构造方法的方式只能使用未关联的超类方法.