python类继承之__init__函数覆盖问题

目录

1.问题描述

2.代码演示

3.总结


在Python这个广受欢迎的编程语言中,类继承是一项强大而精巧的特性。通过类继承,我们可以构建出更加灵活、可重用和易维护的代码,同时实现代码的模块化和扩展性。

但是如果对于熟悉C++和java的人而言,首次接触python的类继承,可能会因__init__函数同名覆盖问题而困惑。下面将详细介绍该问题。

1.问题描述

python中__init__函数可以类比为C++中的构造函数,都是对成员变量进行初始化。当创建子类对象的时候,默认情况下,C++中会先调用父类的默认构造函数,在调用子类的默认构造函数。而在python由于初始化函数都是__init__,那么创建子类对象的时候,存在同名函数问题,那会默认调用父类的__init__函数嘛?如果没有调用会发生什么情况?应该怎么解决呢?

2.代码演示

python中创建子类,__init__函数的调用可以分为三种情况来验证:
(1)子类重写了__init__函数,显式调用父类__init__

(2)子类重写__init__函数,未显式调用父类__init__

(3)子类未重写__init__函数,未显式调用父类__init__

class CBase:
    def __init__(self) -> None:
        print("this is Base init")
        self.test_str="Base"
        self.test_str2="Base2"
        
?
?
class CSub1(CBase):
    def __init__(self) -> None:
        super().__init__()
?
?
class CSub2(CBase):
    def __init__(self) -> None:
        print("this is sub2 init")
?
class CSub3(CBase):
    def test_sub3_func(self):
        print("this is test_sub3_func")
?
if __name__=="__main__":
    t1=CSub1()
    t2=CSub2()
    t3=CSub3()

执行结果

根据执行结果,我们可以得出以下结论:
子类如果重写了__init__函数,父类__init__函数需要显式调用才会执行

子类如果没有重写__init__函数,父类__init__函数隐式调用。

这种行为是由Python的继承机制所决定的。当一个类继承另一个类时,子类继承了父类的属性和方法。但是,如果子类重写了父类的方法,并且在重写的方法中没有显式调用父类的相应方法,那么父类的方法将不会被执行。

那么接下来,如果子类重写__init__函数,但又没有显式调用父类__init__函数,会发生什么情况,如下代码,我们试图在派生类中打印基类的成员变量test_str?:

class CBase:
    def __init__(self) -> None:
        print("this is Base init")
        self.test_str="Base"
        self.test_str2="Base2"
    
class CSub2(CBase):
    def __init__(self) -> None:
        print("this is sub2 init")
        print(self.test_str)

执行结果:

可以看到结果报错了,子类中没有这个?成员变量。

根据执行结果,在父类__init__函数中创建的类属性,子类都无法访问。这一点和C++中有所不同。在C++中,考虑public权限和public继承的情况下,子类一定可以访问到父类的成员变量。

这是因为,在C++这样的静态语言中,创建对象时,成员变量会在对象创建时一同创建。在定义类时,编译器会根据类的定义为每个对象分配内存,并初始化成员变量。

?而在Python这样的动态语言中,对象的属性并不在创建对象时就创建。相反,Python的对象是在运行时动态创建的,并且可以在任何时候添加、修改或删除对象的属性。

而在继承的过程中,子类重写了__init__函数,导致父类的__init__函数没有执行,因此,父类__init__函数中的属性并没有创建,子类访问时必然出错。这一点可以推广到子类和父类的同名函数中。

3.总结

Python是动态语言,其类继承有两个特点:

  • 如果派生类和基类中存在同名函数,派生类的函数会覆盖基类函数,即使是构造函数__init__也不例外。因此,如果要调用基类的同名函数,需要使用super显式调用?。

  • Python是动态语言,类的成员变量是允许动态创建的,因此,当创建派生类对象时,会创建基类对象,但不会为基类对象创建成员变量,此时派生类直接访问基类的成员变量会报错