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)。也就是说:
- 当在 D 的实例上调用一个方法时,Python 首先在 D 中查找。
- 如果没有找到,它会接着去 B 中查找。
- 如果 B 中也没有,它会去 C 中查找。
- 如果 C 中还没有,它会去 A 中查找。
- 最后,如果所有父类都没有,它会到最顶层的 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 中的下一个方法自行处理它们。
