在上一篇《手把手陪您学Python》42——类的魔法方法中,我们学习了如何通过实例方法去定义实例属性,并且对__init__()方法的应用进行了介绍。
今天,我们将会继续深入学习面向对象编程,介绍类的继承和魔法方法在其中的应用。
我们之前学习的类的实例化,是指将类具体到某一特定的事物的过程,这个事物就是类的实例,他具有类的全部属性和方法。
类的继承实际上也具有原来类的全部属性和方法,只不过他不是从原来类具体化成的某一特定事物,而是在原来类的基础上派生出来的新的类。他既具有原来类的全部属性和方法,同时还可以有只属于自己的属性和方法,更重要的是,因为他是一个类,所以还可以再实例化。
继承出来的类和原来的类是继承或者说派生的关系,原来的类称之为“父类”,继承出来的新类称之为“子类”。
1、子类的创建
在创建子类时,父类必须同时包含在当前文件中,而且要位于子类的前面。
定义继承类的方法与定义类的方法近似,也是使用class关键字,也包括“:”和缩进的格式要求,只不过在继承类名称的后面要在括号里指定父类的名称,以告诉Python他们之间的“父子”关系,这时新的“子类”才能够继承“父类”的属性和方法。
class 子类名称(父类名称):
语句1
语句2
。。。
下面,让我们继续请出马里奥先生,给我们展示类的继承的使用和效果。
我们可以将吃了“花”之后的马里奥设置为马里奥类的继承类,名字就叫做超级马里奥吧。虽然他具有马里奥类的一些属性和方法,但还有自己独特的属性和方法,比如帽子变成了白色,攻击方式也可以发射子弹了。
这种在子类中定义与父类不同的属性或者方法称之为“重写”或者“覆盖”。
In [1]: class Mario: # 先定义马里奥类
life = 3
cap = "red"
def jump(self):
print("Mario is jumping!")
def attack(self,name):
print("Mario attacked an anomy!")
In [2]: class Super_Mario(Mario): # 定义马里奥类的继承类
cap = "write" # 只需要定义不同的属性
def attack(self,name): # 只需要定义不同的方法
print("{} shot a bullet!".format(name))
In [3]: print(Super_Mario.life) # 继承父类的属性
print(Super_Mario.cap) # 重写父类的属性
Super_Mario.jump() # 由于在子类中定义的是实例方法,所以类的方法会报错
out[3]: 3
write
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-d262ae1d363e> in <module>
1 print(Super_Mario.life) # 继承父类的属性
2 print(Super_Mario.cap) # 重写父类的属性
----> 3 Super_Mario.jump() # 由于在子类中定义的是实例方法,所以类的方法会报错
TypeError: jump() missing 1 required positional argument: 'self'
In [4]: Super_big_Mario = Super_Mario() # 子类的实例化
Super_big_Mario.size = "big" # 定义实例属性
In [5]: print(Super_big_Mario.life) # 引用类属性
print(Super_big_Mario.cap) # 引用类属性
print(Super_big_Mario.size) # 引用实例属性
Super_big_Mario.jump() # 引用实例方法
Super_big_Mario.attack("Super big Mario") # 引用实例方法
Out[5]: 3
write
big
Mario is jumping!
Super big Mario shot a bullet!
在上面的程序中,我们验证了:
1、通过定义类以及继承类,子类可以继承父类的属性和方法,同时子类还可以定义自己的类属性和实例方法;
2、子类实例化后,可以引用没有被子类覆盖的父类的属性和方法,也可以引用子类自己的类属性和实例方法,还可以定义自己的实例属性。
如果我们能够数量掌握之前学习过的语法规则和概念,理解上面继承类的语法规则和程序应该是比较容易的。只需要区分哪些是父类的属性和方法,哪些是子类覆盖父类的属性和方法,然后在引用时加以区分就可以了。
2、子类的魔法方法
上面我们通过最基本的方式创建了父类、子类的继承关系,并验证了一系列的属性和方法。接下来,我们将在子类中引用上一篇学习的魔法方法,看看又会有哪些神奇的效果。
In [6]: class Mario: # 先定义父类马里奥
def __init__(self, life, cap, name, food): # 保留之前使用的四个参数
self.life = life
self.cap = cap
print("{} has {} lifes.".format(name, self.life))
print("{} has a {} cap.".format(name, self.cap))
self.jump()
self.eat(name, food)
self.attack(name)
def jump(self):
print("Mario is jumping!")
def attack(self,name):
print("{} attacked an anomy!".format(name))
def eat(self, name, food):
print("Mario ate a {}!".format(food))
print("{} became bigger!".format(name))
In [7]: class Super_Mario(Mario):
def __init__(self, life, cap, name, food): # 初始化父类的属性
super().__init__(life, cap, name, food) # super()用来调用父类的方法
In [8]: Super_big_Mario = Super_Mario(30, "White", "Super Mario", "red mushroom") # 定义子类实例
Out[8]: Super Mario has 30 lifes.
Super Mario has a White cap.
Mario is jumping!
Mario ate a red mushroom!
Super Mario became bigger!
Super Mario attacked an anomy!
虽然在上面的代码中加入了部分注释,但可能看起来还是会有些不知所云,那么就让我们一点一点来解释。
首先,定义父类的代码应该没有什么问题,和我们上一篇介绍魔方方法时的代码是一样的。
其次,在定义子类时,只有一个方法,而且是一个__init__()方法。根据我们之前学习的内容,在定义子类实例的时候,__init__()方法将会自动执行,__init__()方法中的参数将在定义子类实例的同时被定义。
最后,非常神奇的一点是super()函数的应用,他让子类能够调用父类的方法,也就是说super().__init__(life, cap, name, food)这样一行代码所起到的作用,是调用父类的__init__()方法,而父类__init__()方法的参数又来自于子类__init__()方法的参数,也就是在定义子类实例的同时定义了父类的属性!
这里,我们又一次体验了魔法方法的神奇之处。子类实例不仅能够定义子类的属性,还能够同时定义父类属性!
以上就是对类的继承的介绍,除了__init__()方法中的一些内容外,都是我们在介绍类的概念时都学习过的内容,这里一方面介绍了类的继承以及子类的创建,同时也对之前学习的内容进行了总结。
下一篇,我们将会介绍面向对象的最后一部分内容——类的导入,敬请关注。
感谢阅读本文!如有任何问题,欢迎留言,一起交流讨论^_^
要阅读《手把手陪您学Python》系列文章的其他篇目,请关注公众号点击菜单选择,或点击下方链接直达。
《手把手陪您学Python》3——PyCharm的安装和配置
《手把手陪您学Python》5——Jupyter Notebook
For Fans:关注“亦说Python”公众号,回复“手43”,即可免费下载本篇文章所用示例语句。
