目录
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是动态语言,类的成员变量是允许动态创建的,因此,当创建派生类对象时,会创建基类对象,但不会为基类对象创建成员变量,此时派生类直接访问基类的成员变量会报错