Python super() 函数

Python super() 语法

super() 是 Python 的一个内置函数,它用于调用父类的方法。当子类重写了父类的方法后,如果想要在子类中调用父类的方法,就可以使用 super() 函数。

语法:

super(type, object_or_type)

说明:

super() 函数接收以下 2 个参数。

  • type(可选):子类的类型,默认自动推导为当前类。
  • object_or_type(可选):子类的实例或类。如果省略该参数,则表示使用当前作用域的 self 或 cls。

super() 最常用的形式是无参数形式。它在类的方法内部调用,不需要传递任何参数,然后会自动推断出当前的类和实例。比如:super().__init__(...)。

Python super() 摘要

使用频率
官方文档 查看
相关函数 isinstance()issubclass()

Python super() 示例

接下来,我们通过几个简单的例子来讲解一下 Python super() 函数是如何使用的。

示例 1:super() 调用父类构造函数

class Animal:
    def __init__(self, type):
        self.type = type

class Dog(Animal):
    def __init__(self, type, color):
        super().__init__(type)
        self.color = color

class Cat(Animal):
    def __init__(self, type, color):
        super().__init__(type)
        self.color = color

dog = Dog('大黄', '黄色')
cat = Cat('小白', '白色')

print(dog.color)
print(cat.color)

运行结果如下。

黄色
白色

分析:

在上面例子中,我们定义了一个父类(Animal)和两个子类(Dog 和 Cat)。在子类的构造函数(__init__())中,我们调用了 super() 函数来执行父类的构造函数,以便正确初始化父类的属性。

示例 2:super() 调用父类普通方法

class Animal:
    def make_sound(self):
        return '发出声音'

class Dog(Animal):
    def make_sound(self):
        # 调用父类的方法
        parent_sound = super().make_sound()
        return f'{parent_sound}:汪汪汪!'

# 创建 Dog 实例并调用方法
dog = Dog()
print(dog.make_sound())

运行结果如下。

发出声音:汪汪汪!

分析:

在上面例子中,我们定义了一个父类 Animal 和一个子类 Dog。父类中有一个普通方法 make_sound(),子类通过 super() 来调用父类的该方法,并在此基础上扩展自己的逻辑。这种方式可以在子类中复用父类的实现,同时添加新的功能。

示例 3:在类方法中使用 super()

class Animal:
    @classmethod
    def describe(cls):
        return '这是一种动物'

class Dog(Animal):
    @classmethod
    def describe(cls):
        # 调用父类的 describe() 方法
        parent_description = super().describe()
        return f'{parent_description}。具体来说,这是一只狗'

# 调用 Dog 的类方法
print(Dog.describe())

运行结果如下。

这是一种动物。具体来说,这是一只狗

分析:

在上面例子中,我们定义了一个父类 Animal 和一个子类 Dog,并在子类的类方法中使用 super() 调用父类的类方法,以实现功能的复用和扩展。

深入理解 super()

super() 函数在 Python 的继承中扮演着非常重要的角色。要透彻搞懂它,我们认真理解以下几个方面。

1. super() 与直接调用父类的区别

肯定有小伙伴会想:“在类的继承中,为什么不用 ParentClass.method(self) 这种方式直接调用父类方法,而非要用 super() 呢?” 其实,它们之间是有本质的区别的,尤其是在处理多重继承时:

(1)super() 函数

super() 是一种动态的方法调用方式。它不会简单地跳到 “直接的父类”,而是根据当前类的 “方法解析顺序(MRO)” 来查找下一个要调用的方法。

也就是说,在多重继承的复杂场景中,super() 能够确保每个父类的方法(在 MRO 链上)只被调用一次且按正确顺序调用,从而避免了重复初始化或方法遗漏等问题。

(2)直接调用父类

Parent.method(self) 这种方式是静态绑定的。它会直接调用你指定类(Parent)的特定方法,而不考虑 MRO。

在多重继承中,这可能导致父类方法被重复调用(如果多个继承路径都指向同一个基类),或者遗漏了 MRO 中其他类的相关方法。这会破坏继承链的预期行为,使代码难以维护和调试。

因此在实际开发中,我们应该始终优先使用 super() 函数,因为它遵循 MRO 动态解析,是处理复杂继承关系最合适的方式。

2. 在静态方法中的限制

super() 的工作原理依赖于它能够获取到当前类和实例(或类本身)。然而,静态方法没有隐式的 self(实例)或 cls(类)参数。

因此,在静态方法中使用 super() 时,你必须显式地传递当前类名 2 次:一次作为 type 参数,一次作为 object_or_type 参数。

示例 4:在静态方法中使用 super()

class BaseClass:
    @staticmethod
    def base_static_method():
        print('这是 BaseClass 的静态方法')

class MyClass(BaseClass):
    @staticmethod
    def my_static_method():
        print('这是 MyClass 的静态方法')
        # 在静态方法中调用父类的静态方法,必须显式传递类名
        super(MyClass, MyClass).base_static_method()

# 调用静态方法
MyClass.my_static_method()

运行结果如下。

这是 MyClass 的静态方法
这是 BaseClass 的静态方法

分析:

在 MyClass 的静态方法 my_static_method() 中,我们调用父类的静态方法时使用了 super(MyClass, MyClass).base_static_method()。这种显式传递参数的方式确保了 super() 能够正确解析父类。

不过需要注意的是,但在实际开发中,在静态方法中使用 super() 并不常见,因为它增加了代码的复杂性。通常来说,如果你需要在静态方法中访问父类功能,可能会重新考虑设计,或者直接通过 ParentClass.static_method() 显式调用。

3. 方法解析顺序(MRO)

MRO 是 Python 处理继承(尤其是多重继承)的核心机制。它定义了 Python 在查找方法或属性时,应该沿着类的继承链向上搜索的确切顺序。super() 正是严格遵循这个 MRO 来确定下一个要调用的方法。

Python 使用一种名为 “C3 线性化算法” 来确定 MRO,这种算法确保了 MRO 是一致的,并且尊重了继承的顺序。其中,我们可以通过访问类的 .__mro__ 属性来查看任何类的 MRO。

示例 5:查看类的 MRO

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C): # D 继承自 B 和 C
    pass

# 查看 D 的 MRO
print(D.__mro__)

运行结果如下。

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

分析:

从输出结果可以看出,D 的 MRO 是 (D, B, C, A, object)。也就是说:

  1. 当在 D 的实例上调用一个方法时,Python 首先在 D 中查找。
  2. 如果没有找到,它会接着去 B 中查找。
  3. 如果 B 中也没有,它会去 C 中查找。
  4. 如果 C 中还没有,它会去 A 中查找。
  5. 最后,如果所有父类都没有,它会到最顶层的 object 类中查找。

常见问题

1. 为什么在多重继承中,super() 调用了非直接父类的方法?

这是一个关于 super() 最常被误解的地方。super() 并不是简单地调用 “你的直接父类” 的方法。相反,它严格遵循 “方法解析顺序(MRO)” 来查找下一个要调用的方法。

也就是说,在多重继承的复杂继承链中,super() 可能会跳过你以为的 “直接父类”,而调用 MRO 中排在下一个的兄弟类(或更上层的类)中的方法。这是 super() 的设计核心,旨在确保所有相关的基类方法都能在正确的时机被调用一次,从而优雅地解决多重继承的 “菱形问题”。

2. 能否在函数外部使用 super()?

不能。 super() 函数是依赖于当前执行上下文来确定其行为的。它需要知道它是从哪个类的方法中被调用的,以及这个方法是针对哪个实例(或类本身)执行的,这样才能正确地在 MRO 中查找目标。

因此,super() 只能在类的定义内部的方法中使用(包括实例方法、类方法和少数情况下的静态方法)。如果你尝试在类或函数外部直接调用 super(),它将无法确定上下文,从而导致错误。

3. 如何处理父类方法签名不一致的问题?

在多重继承中,如果不同的父类有同名的方法,但它们的参数签名(即期望的参数数量和类型)不一致,使用 super() 可能会变得更加复杂。Python 不会强制要求父类的方法签名完全一致,但在调用时如果参数不匹配,仍然会引发 TypeError 异常。

为了在这种情况下安全地使用 super(),我们通常有以下 2 种策略:

  • 确保重写方法时参数兼容:这是最理想的情况。如果可能,让所有同名方法都接受一套 “通用” 的参数,或者在子类中重写时,能够兼容父类的参数列表。
  • 使用 *args 和 **kwargs 传递参数:这种方法最为灵活和健壮。在子类中调用 super() 时,你可以将所有额外的位置参数 (*args) 和关键字参数 (**kwargs) 传递过去,让 MRO 中的下一个方法自行处理它们。

上一篇: delattr()

下一篇: property()

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号