1.覆盖父类方法, 重写子类方法的实现
这种重写方式适用于父类的方法实现和子类的方法实现完全不同。既然完全不同, 那么就在子类中定义一个跟父类方法同名的方法, 并且实现一下就可以。
在实际开发中可能会碰到一种情况, 如果父类封装的方法不能满足子类的需求, 我们就需要使用到方法重写, 在子类中重新编写一下父类的方法实现就好了, 这个就是重写的概念。具体的表现就是在子类中定义一个跟父类同名的方法, 并且自己实现一下就好了。
# 没有重写父类方法时
class Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
# 让 Dog() 类继承自 Animal 类
class Dog(Animal):
def bark(self):
print("汪汪叫")
#Dog 类封装完成
# 让XiaoTianQuan 这个类继承自Dog类
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
# 使用XiaoTianQuan 这个类创建一个对象
xtq = XiaoTianQuan()
xtq.bark() # 输出 汪汪叫
输出结果:
先把XiaoTianQuan这个类展开,在XiaoTianQuan这个类中, 我们现在只是封装了一个飞的方法, 如果我们在类的外部使用XiaoTianQuan 这个类创建一个对象 xtq, 创建完成, 我们让xtq 先来调用一下叫唤的方法, xtq.bark(),执行程序,xtq 的叫声是“汪汪叫”。在子类中重新定义一个bark() 的方法, def bark(self): print("叫得跟神一样"), 我们在XiaoTianQuan这个类中重新定义了一个叫唤的方法, 并没有对父类进行任何的调整, 也没有对创建对象和以及调用方法的代码进行调整, 执行程序, xtq 的叫声是“叫得跟神一样”。
# 重写父类方法 > 在子类中定义一个和父类同名的方法
class Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
# 让 Dog() 类继承自 Animal 类
class Dog(Animal):
def bark(self):
print("汪汪叫")
#Dog 类封装完成
# 让XiaoTianQuan 这个类继承自Dog类
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
# 重写父类的方法
def bark(self):
print("叫得跟神一样...")
# 使用XiaoTianQuan 这个类创建一个对象
xtq = XiaoTianQuan()
xtq.bark() # 输出 汪汪叫
运行结果:
通过代码改造之后, xtq 再一次调用叫唤这个方法时, 既然父类方法不能满足需求, 就在子类中重新编写一下父类的方法, 这样再让xtq这个子类对象调用叫唤方法时, 会执行子类的方法,而不是父类的方法。
2.扩展父类方法, super对象调用父类方法
这是另外一种对父类的方法进行重写的方式, 在实际开发中,有这样一种情况, 子类的方法实现包含有父类的方法实现, 用大白话来讲, 父类方法中提供的功能要少一点, 而子类方法中需要对父类提供的功能进行扩展, 在原有封装的方法基础上, 再增加一些额外的功能, 这个叫做对父类方法的扩展。如果我们需要对父类的方法进行扩展, 第一步仍然需要在子类中先重写一下父类的方法, 也就是在子类中同样需要定义一个跟父类同名的方法, 然后我们在这个方法中在需要的位置, 使用super 这个特殊的类创建一个特殊的对象, 让这个特殊对象调用一下父类方法就可以。
super 在python 中是一个特殊类, super这个类后面跟上一对小括号(), 就可以创建一个对象, 而在我们开发中super这个对象最常见的应用场景,就是当我们在重写父类方法时, 让super 类创建出来的对象来调用一下原本在父类中封装的方法实现,super 创建出来的对象就是来调用父类中封装的方法的,这样在子类中就能调用父类的封装方法, 那么就可以在子类重写的这个方法的其他位置来针对子类特有的需求,编写特有的代码, 这个就是对父类方法的扩展。
如果要让XiaoTianQuan拥有两个拥有两种叫法, 神狗叫法“叫得跟神一样”, 狗的本质叫法“汪汪叫”。这两种叫法都编写入bark 方法中, 在bark 方法中可以做两件事情, 第一步, 针对子类特有的需求,编写代码, 直接编写输出print(“神一样的叫唤...”)第二步, 保留父类中已经封装好的方法, 使用super(). 来调用原来在父类中封装的方法, 敲上super,再跟上一对小括号(),就创建了一个特殊的对象, super(),再通过super().bark() 来调用叫唤的方法 。第三步,当然,在调用完成之后还可以增加其他的代码, 即增加其他子类的代码, 比如 xtq 会发出一些非人类的声音, print(“¥#%……%¥##¥”)。运行程序后,xtq 会发出三种叫唤方法。
#
class Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
# 让 Dog() 类继承自 Animal 类
class Dog(Animal):
def bark(self):
print("汪汪叫")
#Dog 类封装完成
# 让XiaoTianQuan 这个类继承自Dog类
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
# 重写父类中的方法
def bark(self):
# 1. 针对子类特有的需求,重写父类代码
print("神一样的叫唤...")
# 2. 使用super(). 完整的调用原来在父类中封装的方法
super().bark()
# 3. 增加其他子类的代码
print("$$$#%%$$%^&&&")
# 使用XiaoTianQuan 这个类创建一个对象
xtq = XiaoTianQuan()
xtq.bark()
运行结果:
3.使用父类名调用父类方法
比较推荐的调用父类的方式,使用super类, 但是在python 2.x 的早期版本中, 是没有super 这个特殊对象的, 在python 2.x 的早期版本中,如果想要调用父类的方法, 语法格式为:父类名.方法(self), 同时注意我们需要把self 作为第一个参数传递给这个方法, 之前介绍self 的时候, 我们只需要在封装方法的时候, 把self 定义为第一个形参, 而调用方法时解释器会帮我们自动传递self, 但是如果在子类的方法中, 希望调用父类的方法, 我们就需要把self 作为第一个参数传递给这个方法。 但在python3.x 中是不推荐使用这种方式来调用父类方法的, 原因有两个, 第一个,语法的格式有点特殊, 不是很好记忆, 第二个, 如果我们一旦修改了父类, 就以为着应该在子类的方法中把所有调用方法的代码全部需要替换一下, 才能够完成修改父类的这个动作, 因此,这种方式是不推荐使用的。
# 使用父类名调用父类方法
class Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
# 让 Dog() 类继承自 Animal 类
class Dog(Animal):
def bark(self):
print("汪汪叫")
#Dog 类封装完成
# 让XiaoTianQuan 这个类继承自Dog类
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
# 重写父类中的方法
def bark(self):
# 1. 针对子类特有的需求,重写父类代码
print("神一样的叫唤...")
# # 2. 使用super(). 完整的调用原来在父类中封装的方法
# super().bark()
# 父类名.方法(self)
Dog.bark(self)
# 3. 增加其他子类的代码
print("$$$#%%$$%^&&&")
# 使用XiaoTianQuan 这个类创建一个对象
xtq = XiaoTianQuan()
xtq.bark()
运行结果:
现在把第33行代码super().bark() 注释一下, 再让xtq 执行叫唤方法时, 就不会调用到父类的方法. 现在就使用 XiaoTianQuan这个类的父类, 也就是Dog()类来调用一下叫唤的方法, 并且把self 作为参数传递进来, Dog.bark(self), 代码修改完成, 运行一下程序, xtq 又能像其他狗一样“汪汪叫”了。这个就是使用父类名称来调用父类方法的一种方式。我们方法的第一个参数传递了self, 如果不传递self, 代码执行到36行就会报错了, 第36行的代码执行时, 控制台信息告诉我们, 缺少一个必须传递的参数self。
正确写法:
错误写法:
输出结果:控制台信息告诉我们, 缺少一个必须传递的参数self。
如果在开发时,不小心将Dog 这个类写成XiaoTianQuan 这个类, 先写上XiaoTianQuan这个类,然后让这个类来调用一下bark()方法, 并且把self 作为参数传递进来, XiaoTianQuan.bark(self)运行一下程序, 控制台告诉我们第30行代码出错了, 向上滚动, 错误的信息非常多, 出错的代码全部集中在第37行, XiaoTianQuan.bark(self), 分析: 在XiaoTianQuan 这个类的bark 方法中, 我们再调用了一下 XiaoTianQuan 这个类的bark 方法, 这种方式叫做递归调用, 而递归调用如果没有出口, 就会出现死循环。
# 使用父类名调用父类方法
class Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
# 让 Dog() 类继承自 Animal 类
class Dog(Animal):
def bark(self):
print("汪汪叫")
#Dog 类封装完成
# 让XiaoTianQuan 这个类继承自Dog类
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
# 重写父类中的方法
def bark(self):
# 1. 针对子类特有的需求,重写父类代码
print("神一样的叫唤...")
# # 2. 使用super(). 完整的调用原来在父类中封装的方法
# super().bark()
# 父类名.方法(self)
# Dog.bark(self)
# 注意: 如果使用子类调用方法, 会出现递归调用,形成死循环
XiaoTianQuan.bark(self)
# 3. 增加其他子类的代码
print("$$$#%%$$%^&&&")
# 使用XiaoTianQuan 这个类创建一个对象
xtq = XiaoTianQuan()
xtq.bark()
报错信息:
在开发时, 父类名和super() 两种方式不要混用。
如果使用当前子类名调用方法, 会形成递归调用, 出现死循环。