在 Python 类中使用 super() 函数

核心要点

  • Python中的super()函数允许从子类调用父类方法,简化了继承和方法覆盖的实现。
  • super()函数与Python的方法解析顺序(MRO)密切相关,后者决定了在父类中搜索方法或属性的次序。
  • 在类的构造函数中使用super()是一种常见做法,用于初始化父类中的通用属性以及子类中的特定属性。不使用super()可能会导致意外问题,例如属性初始化遗漏。

Python的核心特性之一是其面向对象编程(OOP)范式,它可以用来模拟现实世界中的实体及其关系。

在Python类中,经常会使用继承并覆盖父类的属性或方法。Python提供了super()函数,使得从子类调用父类方法变得方便。

什么是super(),以及为什么需要它?

通过继承,你可以创建一个新的Python类,该类继承现有类的功能。你还可以在子类中重写父类的方法,提供不同的实现。然而,有时你不仅想使用新功能,还想保留旧功能,而不是完全替换它。在这种情况下,super()就非常有用。

你可以使用super()函数来访问父类的属性并调用其方法。super()在面向对象编程中至关重要,因为它简化了继承和方法重写的实现。

super()是如何工作的?

在内部,super()与Python的方法解析顺序(MRO)紧密相连,后者由C3线性化算法确定。

super()的工作方式如下:

  • 确定当前类和实例:当你在子类的方法中调用super()时,Python会自动识别当前类(包含调用super()的方法的类)以及该类的实例(即self)。
  • 确定父类super()接受两个参数——当前类和实例——但你无需显式传递它们。它使用这些信息来确定委托方法调用的父类,通过检查类层次结构和MRO来实现。
  • 调用父类的方法:一旦确定了父类,super()允许你调用其方法,就像直接从子类调用一样。这使你能够扩展或覆盖方法,同时仍然利用父类的原始实现。

在类的构造函数中使用super()

在类的构造函数中使用super()是很常见的做法,因为你通常希望初始化父类中的公共属性以及子类中更具体的属性。

为了演示这一点,我们定义一个Python类Father,然后让Son类继承自它:

      
 class Father:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

class Son(Father):
    def __init__(self, first_name, last_name, age, hobby):
        
        super().__init__(first_name, last_name)

        self.age = age
        self.hobby = hobby

    def get_info(self):
        return f"Son's Name: {self.first_name} {self.last_name}, \
Son's Age: {self.age}, Son's Hobby: {self.hobby}" son = Son("Pius", "Effiong", 25, "Playing Guitar") print(son.get_info())

Son构造函数中,调用super().__init__()会调用Father类的构造函数,并将first_namelast_name作为参数传递给它。这确保了Father类能够正确设置名称属性,即使是在Son对象上也是如此。

如果在类构造函数中不调用super(),则父类的构造函数不会执行。这可能导致意外问题,例如缺少属性初始化或父类状态设置不完整:

        
  ...
class Son(Father):
    def __init__(self, first_name, last_name, age, hobby):
        self.age = age
        self.hobby = hobby
...
        
    

如果现在尝试调用get_info方法,它将引发AttributeError,因为self.first_nameself.last_name属性尚未初始化。

在类方法中使用super()

除了构造函数之外,你也可以在其他方法中使用super(),方式相同。这使你能够扩展或覆盖父类方法的行为。

        
class Father:
    def speak(self):
        return "Hello from Father"

class Son(Father):
    def speak(self):
        
        parent_greeting = super().speak()
        return f"Hello from Son\n{parent_greeting}"


son = Son()

son_greeting = son.speak()

print(son_greeting)
        
    

Son类继承自Father并有自己的speak方法。Son类的speak方法使用super().speak()来调用Father类的speak方法。这使其能够包含来自父类的消息,同时使用特定于子类的消息进行扩展。

如果在覆盖父类方法的方法中未使用super(),则意味着父类方法中的功能将失效。这会导致方法行为被完全替换,这可能会导致意料之外的结果。

理解方法解析顺序

方法解析顺序(MRO)是Python在访问方法或属性时搜索父类的顺序。当存在多个继承层级时,MRO帮助Python确定应该调用哪个方法。

        
class Nigeria():
    def culture(self):
        print("Nigeria's culture")

class Africa():
    def culture(self):
        print("Africa's culture")

class Lagos(Africa, Nigeria):
    pass


lagos = Lagos()
lagos.culture()
        
    

当你创建Lagos类的实例并调用culture方法时,会发生以下情况:

  • Python首先在Lagos类本身中查找culture方法。如果找到,则调用该方法。如果没有找到,则继续执行第二步。
  • 如果在Lagos类中没有找到culture方法,Python会按照基类在类定义中出现的顺序查看它们。在这种情况下,Lagos首先从Africa继承,然后从Nigeria继承。因此,Python首先会在Africa中寻找culture方法。
  • 如果在Africa类中没有找到culture方法,Python将在Nigeria类中查找。这个过程会持续到到达继承层级的末尾,如果在任何父类中都没有找到该方法,则会抛出错误。

输出显示了Lagos的方法解析顺序,从左到右。

常见陷阱和最佳实践

使用super()时,需要避免一些常见的陷阱。

  • 注意方法解析顺序,特别是在多重继承场景中。如果需要使用复杂的多重继承,应该熟悉Python用于确定MRO的C3线性化算法
  • 避免类层次结构中的循环依赖,这可能会导致不可预测的行为。
  • 清楚地记录你的代码,特别是在复杂的类层次结构中使用super()时,以便其他开发者更容易理解。

正确使用super()

当你处理继承和方法重写时,Python的super()函数是一个强大的工具。理解super()的工作原理并遵循最佳实践,将使你在Python项目中创建更易于维护和高效的代码。