杰瑞科技汇

python init_继承

核心要点

在 Python 中,子类的 __init__ 方法默认不会自动调用父类的 __init__ 方法,如果你希望在创建子类实例时,先执行父类的初始化逻辑(设置父类共有的属性),你必须在子类的 __init__ 方法中显式地调用父类的 __init__ 方法。

python init_继承-图1
(图片来源网络,侵删)

为什么需要调用父类的 __init__

父类的 __init__ 方法通常用于初始化该类实例所必需的属性,子类继承了父类的属性和方法,但子类实例本身并没有执行父类的 __init__ 代码,因此这些属性并未被正确初始化。

让我们看一个没有调用父类 __init__ 的例子:

class Animal:
    def __init__(self, name, species):
        print("Animal 的 __init__ 被调用")
        self.name = name
        self.species = species
    def make_sound(self):
        print("动物发出声音")
class Dog(Animal):
    def __init__(self, name, breed, color):
        print("Dog 的 __init__ 被调用")
        self.breed = breed
        self.color = color
# 创建一个 Dog 实例
my_dog = Dog("旺财", "中华田园犬", "黄色")
# 尝试访问父类的属性
try:
    print(my_dog.name)  # 这会报错!
except AttributeError as e:
    print(f"错误: {e}")
# 尝试调用父类的方法
my_dog.make_sound() # 这可以工作,因为方法已经被继承了

输出:

Dog 的 __init__ 被调用
错误: 'Dog' object has no attribute 'name'
动物发出声音

分析:

python init_继承-图2
(图片来源网络,侵删)
  1. 当我们创建 my_dog 实例时,只有 Dog 类的 __init__ 方法被调用了。
  2. Animal 类的 __init__ 方法从未被执行,self.nameself.species 这两个属性没有被创建。
  3. 当我们尝试访问 my_dog.name 时,Python 找不到这个属性,抛出 AttributeError
  4. make_sound 方法之所以能调用,是因为方法是通过继承机制传递下来的,它不需要在实例化时“创建”。

如何正确调用父类的 __init__

我们有几种方式可以调用父类的 __init__ 方法,最常用和推荐的是使用 super()

使用 super() (推荐)

super() 是一个内置函数,它返回一个代理对象,该对象允许你调用父类(或兄弟类)的方法,这是现代 Python(Python 3)中最标准、最清晰的方式。

语法: super().父类方法名(参数列表)

示例:

python init_继承-图3
(图片来源网络,侵删)
class Animal:
    def __init__(self, name, species):
        print("Animal 的 __init__ 被调用")
        self.name = name
        self.species = species
    def make_sound(self):
        print("动物发出声音")
class Dog(Animal):
    def __init__(self, name, breed, color):
        print("Dog 的 __init__ 被调用")
        # 使用 super() 调用父类 Animal 的 __init__ 方法
        # 注意:这里 self 会自动传递给父类
        super().__init__(name, species="犬科") # 我们可以在这里显式指定 species
        self.breed = breed
        self.color = color
# 创建一个 Dog 实例
my_dog = Dog("旺财", "中华田园犬", "黄色")
# 现在可以成功访问父类的属性了
print(f"名字: {my_dog.name}")
print(f"种类: {my_dog.species}")
print(f"品种: {my_dog.breed}")
print(f"颜色: {my_dog.color}")
# 调用父类的方法
my_dog.make_sound()

输出:

Dog 的 __init__ 被调用
Animal 的 __init__ 被调用
名字: 旺财
种类: 犬科
品种: 中华田园犬
颜色: 黄色
动物发出声音

分析:

  1. Dog__init__ 执行 print("Dog 的 __init__ 被调用")
  2. super().__init__(name, species="犬科") 这行代码,将 my_dog 实例(即 self)以及 name"犬科" 作为参数,传递给了 Animal 类的 __init__ 方法。
  3. Animal__init__ 执行,并设置了 namespecies 属性。
  4. 然后继续执行 Dog__init__,设置 breedcolor 属性。
  5. my_dog 实例拥有了所有需要的属性。

直接调用父类名 (旧式方法,不推荐)

在 Python 2 中或者一些旧的代码中,你可能还会看到这种方式。

语法: 父类名.__init__(self, 参数列表)

示例:

class Dog(Animal):
    def __init__(self, name, breed, color):
        print("Dog 的 __init__ 被调用")
        # 直接调用父类名
        Animal.__init__(self, name, species="犬科")
        self.breed = breed
        self.color = color

为什么不推荐?

  1. 可读性差:代码中硬编码了父类名,如果将来父类名改变,所有子类中的调用都需要修改。
  2. 处理多重继承时复杂:在涉及多重继承的复杂场景下,super() 能更好地处理方法解析顺序,而直接调用父类名可能会导致混乱。
  3. 不够优雅super() 是 Python 为继承设计的专用工具,更符合语言的设计哲学。

多重继承中的 super()

super() 在多重继承中尤其强大,它能正确地遵循 MRO (Method Resolution Order),即方法解析顺序,来依次调用各个父类的方法。

示例:

class A:
    def __init__(self):
        print("A 的 __init__")
        super().__init__() # 关键:调用下一个 in line 的类的 __init__
class B:
    def __init__(self):
        print("B 的 __init__")
        super().__init__()
class C:
    def __init__(self):
        print("C 的 __init__")
        super().__init__()
class D(A, B, C):
    def __init__(self):
        print("D 的 __init__")
        super().__init__()
print(D.__mro__) # 查看 MRO 顺序
# 输出: (<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
d = D()

输出:

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

分析:

  1. D.__init__ 被调用。
  2. super().__init__() 会找到 MRO 中的下一个类,即 A,并调用 A.__init__()
  3. A.__init__() 中的 super().__init__() 会找到 MRO 中的下一个类,即 B,并调用 B.__init__()
  4. B.__init__() 中的 super().__init__() 会找到 MRO 中的下一个类,即 C,并调用 C.__init__()
  5. C.__init__() 中的 super().__init__() 会找到 MRO 中的下一个类,即 object,并调用 object.__init__()object__init__ 通常是空的,所以没有输出)。
  6. 整个调用链完成。

这个机制确保了每个父类的初始化逻辑都能被按顺序执行。


总结与最佳实践

  1. 默认不调用:子类的 __init__ 不会自动执行父类的 __init__
  2. 必须显式调用:如果子类需要父类的属性被正确初始化,必须在子类的 __init__ 中显式调用父类的 __init__
  3. 首选 super():始终使用 super().__init__(...) 来调用父类的初始化方法,这是最清晰、最灵活、最符合现代 Python 风格的做法。
  4. 传递 self:通过 super() 调用时,self 会自动传递,你只需要提供父类 __init__ 方法所需的其他参数。
  5. 考虑所有参数:在设计类时,一个好的实践是让子类的 __init__ 方法接收所有必要的参数,并将其中一部分传递给父类的 __init__

一个健壮的子类 __init__ 模板:

class Parent:
    def __init__(self, parent_param1, parent_param2):
        self.parent_param1 = parent_param1
        self.parent_param2 = parent_param2
class Child(Parent):
    def __init__(self, parent_param1, parent_param2, child_param):
        # 1. 调用父类的 __init__,并传递它需要的参数
        super().__init__(parent_param1, parent_param2)
        # 2. 初始化子类特有的属性
        self.child_param = child_param
# 使用
child_instance = Child("p1_value", "p2_value", "c_value")
print(child_instance.parent_param1) # 输出: p1_value
print(child_instance.child_param)  # 输出: c_value
分享:
扫描分享到社交APP
上一篇
下一篇