7.3 类继承
继承是代码重用的好办法,Python中的继承就像现实生活中的继承一样,子类可以什么都不做就拥有了父类的属性或方法。
7.3.1 单继承
单继承就是一个子类只有一个基类的继承方式,语法是:class子类名(基类名): …。
还是以汽车为例来讲解单继承。汽车不是只有燃油的传统小汽车,还有特斯拉这种电动车,虽然它们都是汽车,都有品牌、颜色等属性,但是在一些细节上还是有区别的。据此可以定义一个Car基类,它包含所有汽车的通用属性,然后再定义一个OilCar类和ECar类分别代表燃油汽车和电动车:
接下来分别实例化OilCar和ECar类,看看它们是不是真的能够使用基类的属性与方法:
由此可见,对象o是OilCar类型,同时也是Car的一个实例。子类不但可以直接使用父类的属性(brand、color)与方法(print_car),而且还可以增加新方法(power)。
7.3.2 多继承
多继承就是一个子类可以继承多个父类的继承方式,相对于单继承来说,多继承更复杂也更难以控制,容易造成菱形继承问题,即两个父类同时继承了一个基类,而子类会包含多个父类的内容,产生代码歧义,因此很多编程语言都摒弃了这种继承方式如Java和C#。Python是允许多继承的,这对于开发人员来说即提供了更多的代码编写方案,同时也引入了更多的潜在问题,因此开发人员应时刻注意多继承的风险。
虽然编者并不建议开发人员使用多继承的方式编写代码,但是仍在这里对多继承做一个简单介绍,多继承的语法与单继承类似:class子类名(基类1,基类2…): …。下面是一个非常简单的多继承的例子:
类C同时继承了类A和类B,那么它应该同时拥有类A和类B的属性与方法,执行以下代码进行查看:
>>> c = C() >>> c.run(5) 我在以5米/秒的速度跑步 >>> c.speak("你好") 我在说: 你好
上面的代码非常简单,因为类A和类B既没有构造函数也没有重复的属性和方法,那么如果它们都有构造函数并且有同名的方法时会发生什么呢?
执行以下代码:
>>> c = C(18) >>> c.intro() 我叫 18 >>> c.run(5) 我在以5米/秒的速度跑步 >>> c.speak("你好") 我在说: 你好
从上面的执行结果来看,本来我想介绍我的年龄,但是却输出了“我叫18”,而另外两个方法还能正常执行。这种现象是继承顺序导致的,类A在类B的前面,所以对于同名的属性与方法子类都会调用类A的。
上面是普通方法在多继承中的表现,对于构造方法来说就更复杂了。Python中类的构造方法基本按照以下方式执行。
如果子类有自己的构造方法,那么在实例化子类的时候就会执行子类的构造方法,不会执行基类的构造方法,例如:
如果子类没有构造方法,在单继承中则会直接调用基类的构造方法,例如:
如果子类有多个基类并且子类没有自己的构造函数,则会按顺序查找父类,找到第一个有构造函数的基类并执行,例如:
7.3.3 方法重载
有时虽然父类已经提供了一些方法,但是这些方法可能不能满足子类的需求,所以可以在子类中对父类方法进行重写。
还是以单继承为例,前面的例子中子类OilCar和ECar都直接使用了父类的print_car()方法,接下来希望在print_car()函数的输出中还能展示当前车辆的动力类型,此时就需要重写父类的print_car()方法,具体修改如下:
重新调用print_car()方法查看执行结果:
>>> o = OilCar("奔驰", "红色") >>> o.print_car() 品牌: 奔驰 , 颜色: 红色 , 动力:汽油 >>> e = ECar("特斯拉", "黑色") >>> e.print_car() 品牌: 特斯拉 , 颜色: 黑色 , 动力:电池
执行正常,子类已经重写了父类方法,而且不同的子类之间没有干扰。
7.3.4 super函数
仔细观察上面方法重载的例子可以发现,两个子类中print_car()非常相似,只有很少的一部分代码有区别,本着代码重用原则,可以使用super函数在子类中调用父类方法,以达到减少子类代码冗余的目的。
Super是Python的内置函数,可以用来调用父类的方法,这在方法被重载时非常有用。Super函数的语法:super([type[,object-or-type]])。
Super函数有两种用法:①在单继承结构中,super可以隐式地返回父类。②支持多继承,这也是除Python外几乎目前所有编程语言中唯一一种能做到合理使用多继承的方式,super使得开发人员可以很好地解决菱形继承问题。不管哪种用法,super的调用都类似下面形式:
class C(B): def method(self, arg): super().method(arg) #等同于: super(C, self).method(arg)
注意
super这种不传参数的用法只能用在类方法中,Python解释器会自动填充参数。
第二个参数object-or-type一般都是self。
了解了以上知识后,使用super修改上面代码:
7.3.5 访问权限
前面例子中所有类的属性与方法都是公有的,也就是说,子类可以没有限制地使用基类的任何成员。但是有时还需要对类成员的访问权限加以控制,如只允许类内部使用,或者只允许类本身和子类使用。
□ 类的私有属性如下。
__private_attrs:以两个下画线开头,不能在类的外部使用,在类内部使用时:self.__ private_attrs。
□ 类的私有方法:
__private_methods:以两个下画线开头,不能在类的外部使用,在类内部使用时:self.__private_methods。
读者可通过下面的例子看看私有属性与公有属性的区别:
访问公有变量与私有变量:
可见在类外部是不能访问私有变量的,但是其实Python的私有变量是一个伪私有变量,使用dir函数(dir是Python的内置函数,可以用来查看对象的所有属性与方法)查看实例at:
从输出结果可见,at包含了一个属性_AccessTest__private,这个属性其实就是我们前面定义的__private属性,这是Python的名称修饰(name mangling)功能对__private的重命名。我们继续访问这个_AccessTest__private属性,看看它是不是真的等于__private呢?
>>> print(at._AccessTest__private) 1
果然符合预期。