在讲面向对象的第一章中,我们提到了继承,所有类都会默认继承object基类。那么继承的应用是什么?我们设计一个Animal类作为父类,以此为例讲解什么是继承。
class Animal:animal_count = 0def __init__(self, name: str, age: int):Animal.animal_count += 1print(f"Total animal count: {Animal.animal_count}")self.name = nameself.age = agedef eat(self, food: str):print(f"{self.name} is eating {food}.")
一、继承及其特点
面向对象的编程的好处之一是实现代码的重用(如对象调用类方法),实现这种重用的方法之一则是继承机制。通过继承父类、基类或超类创建的新类称为子类或派生类,其语法是“class 子类(被继承的父类)”,其中被继承的父类可以有多个,在括号中以逗号分隔。
类继承有以下的一些特点:
如果在子类使用了父类的构造函数,需要显式的调用父类的构造函数,即“super().__init__()”。
在子类类方法中调用父类的方法时,需要加上父类的类名前缀,且self需作为参数进行传递,即“ParentClass.method(self, args)”;或者使用super调用,此时无需将self作为传参,即“super().method(args)”。
子类中的函数名可以与父类相同,使用子类的对象调用类方法时,先在子类中查找该名称函数,不存在时再逐级去父类中查找。
二、继承的代码表示
(1)继承并创建子类属性及方法
创建Dog类来继承Animal,添加breed作为Dog的特有属性,创建bark作为Dog的特有类方法,创建对象dog,并进行属性的初始化:
class Dog(Animal):def __init__(self, name: str, age: int, breed: str):super().__init__(name, age)self.breed = breedprint(f"Dog {self.name} is the No.{Animal.animal_count}.")def bark(self):print(f"{self.name} is barking.")def eat(self, food: str):print(f"{self.name} will eat. ", end="")Animal.eat(self, food)dog = Dog(name="Fido", age=3, breed="Golden Retriever")# Output:# Total animal count: 1# Dog Fido is the No.1.
(2)调用父类 / 子类中的属性或方法
通过上述例子,我们进行几次类方法的调用:
dog.bark() # Output: Fido is barking.dog.eat("bones") # Output: Fido will eat. Fido is eating bones.
我们解释一下其中的一些写法:
父类的类变量可直接通过“父类.变量名”的方式获取,如“Animal.animal_count”。
使用“父类.类方法”的方式调用父类函数,self需要作为首个传入参数,如Dog的eat_something中调用Animal的eat时,self为第一个参数。
父类的属性可以直接通过“self.属性”得到,如bark中获取父类的name属性。
若子类初始化的同时需要通过父类构造函数初始化父类中的属性,需要显示调用父类构造函数,并按照构造函数参数列表传入相应的值,如Dog的构造函数第一行,通过super调用父类构造函数。
调用bark时,由于其为子类类方法,所以直接执行子类bark中的代码。
调用eat,由于子类中与父类重名的方法会覆盖(重写)父类该方法,因此调用时会执行子类eat中的代码。若子类中未定义eat,则直接调用父类的eat方法。
(3)多重继承的初始化
假设类A使用a作为构造函数的参数,类B使用b作为构造函数的参数;类C作为派生类,继承A和B,那么在C初始化时如何同步初始化A和B?
此时可以使用协作式继承(Cooperative Multiple Inheritance),通过键值对传参调用构造函数的方法,完成同步初始化:
class A(object):def __init__(self, a):self.a = aclass B(object):def __init__(self, b):self.b = bclass C(A, B):def __init__(self, a, b, c):super().__init__(a=a, b=b)self.c = c
也可以分别使用“父类.__init__(self, *args)”的方式进行初始化A、B,但并不推荐此类做法,会出现无法处理菱形继承等问题。
往期回顾:
PyEcharts以地理地图为基础绘制数据分析图表|PyEcharts攻略Part.10
用PyEcharts绘制时间轴图表|PyEcharts攻略Part.9