类的继承
介绍— “错过了落日余晖,别忘了还有满天星辰”
继承是面向对象编程的一个重要概念。它可以让一个类(子类)从另一个类(父类)继承属性和函数,并且可以添加自己特定的属性和函数。
想象一下,假如有一家设计公司设计了一款时尚的汽车产品,这款汽车有很多自己的特点,比如炫酷的颜色、响亮的喇叭、强劲的动力等,在经过一段时间的销售后,公司希望在此基础上造出一款更新的版本,这款最新版本的汽车具备老款的全部特征,但是在此基础上做了一些细微的修改,那么这家公司就没有必要重新进行设计,只需要借鉴老款汽车的模型,重复利用这个模版,在此基础上做修改就可以。
上面的例子就可以理解为编程中的继承概念,其中老款汽车可以理解为 “父类”,新款汽车可以理解为 “子类”,通过子类继承父类,让两种汽车保持了相同的功能结构,但保持部分差异。
通过继承,可以实现代码的重用,提高代码的可维护性和可扩展性。
继承的写法
在 Python 中,类是使用 class 关键词来进行定义,在 class 后的括号内传入父类名称来完成继承的功能,新的子类会默认继承父类的属性和函数。
下面是一个简单的继承的示例,这个示例为了方便新手理解,我不在父类中传入 __init__ 构造函数,在后面的教程中再教学带 __init__ 的继承方法:
先创建一个父类叫 动物,动物具备的功能是吃、喝,然后创建一个子类叫做 狗,狗具备动物父类的全部特点,并在此基础上还会 汪汪叫。
# 创建父类动物
class
Animal
():def
eat
(self
):print
('正在进食'
)def
drink
(self
):print
('正在饮水'
)# 创建子类狗,在括号内填入父类的类名
class
Dog
(Animal):def
bark
(self
):print
('小狗正在旺旺叫'
)# 调用 “狗” 类创建一只狗的对象
Dog
()# 让这只狗执行吃、喝、叫的函数功能
eat
()drink
()bark
()运行上述代码,结果如下:
正在进食
正在饮水
小狗正在旺旺叫
上面的代码中,我创建了一个 动物 的父类,一个 狗 的子类,虽然 狗 的子类中只有一个 bark 函数,但是因为继承了 Animal 父类,所以默认会继承父类中的全部函数功能,因此创建的 狗 的对象,自然而然具备动物类的吃、喝功能,并且自己也有额外的 汪汪叫 功能。
继承的传递
在 Python 中继承是具备传递性的,子类不但可以继承父类的属性和函数,还会继承父类的父类(祖父类)的属性和函数,以此类推。直接来看示例:
# 定义动物类,动物可以进食
class
Animal
():def
eat
(self
):print
('动物可以进食'
)# 定义鸟类,鸟类继承动物类特点并可以飞
class
Bird
(Animal):def
fly
(self
):print
('鸟类可以飞'
)# 定义鸭子类,鸭子类继承鸟类特点并可以游泳
class
Duck
(Bird):def
swim
(self
):print
('鸭子还可以游泳'
)# 调用鸭子类生成一个鸭子的对象
Duck
()eat
()fly
()swim
()运行上面代码,结果如下:
动物可以进食
鸟类可以飞
鸭子还可以游泳
在上述例子中,我在后面调用了最后的鸭子类,鸭子类继承自鸟类,而鸟类又继承自动物类。因此,虽然鸭子类中并没有定义 吃 和 飞 的函数,但是鸭子类继承了鸟类和动物类的所有属性和方法,因此也是可以直接使用的。
多继承
在 Python 中,可以使用多继承的方式让一个子类可以同时继承多个父类的属性和函数,在定义子类的时候可以在类名后面同时传入多个类作为父类来完成继承。直接来看示例:
# 定义动物类,动物类可以进食
class
Animal
():def
eat
(self
):print
('它是动物,可以进食'
)# 定义哺乳动物类,哺乳动物可以胎生
class
Mammal
():def
give_birth
(self
):print
('它是哺乳动物,可以胎生'
)# 定义鱼类,鱼类可以游泳
class
Fish
():def
swim
(self
):print
('它是鱼类,可以游泳'
)# 定义海豚类,并同时继承动物、哺乳动物、鱼类的特点
class
Dolphin
(Animal,Mammal,Fish):pass
# 调用海豚类生成一个海豚的对象
Dolphin
()eat
()give_birth
()swim
()运行上述代码,结果如下:
它是动物,可以进食
它是哺乳动物,可以胎生
它是鱼类,可以游泳
在上面的例子中,我们在最后创建了一个海豚类,这个海豚类同时继承了动物类、哺乳动物类、鱼类的特点,因此海豚具备上面三个类的全部特点和功能。
父类的属性调用
当子类需要继承父类的函数或者属性时,可以使用 super 内置函数来代表父类,通过使用 super().__init__() ,可以将自己的属性传递给父类的 __init__ ,在父类的基础上新增自己的属性。这样,子类和父类都有相同的属性
【案例】来看一个例子方便理解:
# 定义父类
class
Parent
():def
__init__
(self
,name):self
.name = namedef
read_name
(self
):print
('名字是'
+ str
(self
.name))# 创建一个子类,继承父类
class
Child
(Parent):def
__init__
(self
,name,age):self
.age = age# 在这里将自己的属性传递给父类的初始化函数中
super
().__init__(name)def
read_age
(self
):print
('年龄是'
+ str
(self
.age))# 调用子类生成对象
Child
("小明"
,10
)read_name
()read_age
()运行代码,结果如下:
名字是小明
年龄是10
在上面的案例中,我们在 Child 类的构造函数 __init__ 中使用 super 内置函数将子类的 name 属性绑定给父类的 __init__ 构造函数,这样在子类传入 name 属性后,调用父类的函数就可以直接将数据引入进去。可以在结果中看出,子类中传入了 “小明” 这个 name 属性,在调用父类的 read_name 函数,顺利地将属性引入并输出结果。
要注意的是, 在多继承时,super() 代表第一个传入的父类 。
在 Python 中,多继承考虑了所有继承的类的顺序,并确保每个类的方法仅被调用一次。在多继承的情况下,由于我们会在子类中传入多个父类,因此 super 父类如果不指定的话代表的是第一个传入的父类,
如果希望调用多继承中特定的父类,那么需要进行指定,具体的写法是:指定的父类名.函数,注意要在类名后加上 self 关键词,来看一个简单例子帮助理解:
class
A
():def
__init__
(self
,name):self
.name = namedef
name_introduce
(self
):print
('您好,我是'
+self
.name)class
B
():def
__init__
(self
,age):self
.age = agedef
age_introduce
(self
):print
('我的年龄是'
+ str(self
.age) + '岁'
)class
C
(A,B):def
__init__
(self
,name,age):super
().__init__
(name)__init__
(self
,age)def
name_and_age
(self
):self
.name_introduce
()self
.age_introduce
()C
('Tom'
,20
)name_and_age
()运行上述代码,结果如下:
您好,我是Tom
我的年龄是20岁
在上面的代码中,C 类继承了 A 类和 B 类属性和方法,在 C 类中,由于构造函数传入的 age 参数是给 B 类的,但是 super 方法在多继承中默认代表的是第一个父类,也就是 A 类,所以我在这里使用了指定类别的方法。也就是 B.__init__(self,age) 将 age 属性传递给 B 类。
父类函数的重写
在 Python 的继承中,子类会重写(覆盖)父类的函数,也就是说如果子类和父类中出现了名称相同的函数,那么通过子类创建的实例,在调用这个函数时,将会优先执行之类中的函数,而不是父类中的函数。
这样设计的好处是,子类在继承父类的方法后,可以对其进行修改更新,来满足子类的特殊需求。来看一个示例:
【案例】来看一个例子方便理解:
# 定义父类
class
Parent
():def
hello
(self
):print
('这里是父类的输出结果'
)# 创建一个子类,继承父类
class
Child
(Parent):def
hello
(self
):print
('这里是子类的输出结果'
)# 调用子类生成对象
Child
()hello
()运行上述代码,结果如下:
这里是子类的输出结果
在上述示例中,有一个父类 Parent 和一个子类 Child, 父类中定义了一个方法 hello,子类中也定义了同名的方法 hello。子类继承了父类中的属性和方法,在调用这个函数的时候输出的结果是子类中的结果,这是因为子类重写了父类的方法。
父类函数的调用
在上面的教程中,我们学习了如何在子类中覆盖父类的函数,但是在有一些场景中仍然会用到父类同名函数的一些功能,这种情况我们可以通过调用父类的原函数来实现的。
在 Python 中,如果在子类中重写了父类的方法,但仍然希望在子类中调用父类的方法,可以使用 super 函数来实现,我们在上面提到过,在子类中 super 代表父类。继承函数的写法是 super()+ . + 函数名。
【案例】来看一个示例:
# 定义父类
class
Parent
():def
hello
(self
):print
('这里是父类的输出结果'
)# 创建一个子类,继承父类
class
Child
(Parent):def
hello
(self
):# 在这里引用父类的 hello 函数
super
().hello()print
('这里是子类的输出结果'
)# 调用子类生成对象
Child
()hello
()运行上述代码,结果如下:
这里是父类的输出结果
这里是子类的输出结果
在上面的代码中,子类和父类具有相同的函数名称 hello,在之前的教学中我们知道相同名称的函数会被子类覆盖,但是在子类的 hello 函数中引用了 super().hello() 也就是引用了父类的同名函数,这样在运行子类创建的示例时就会再次运行父类的函数。从结果可以看出,输出了两个结果,第一个是父类的,第二个是子类的。
例题
现在设计一款简单的模拟游戏,包括人类(Human)和怪物(Monster)两类。人类可以攻击怪物并造成伤害,怪物可以反击人类造成伤害。每个人类和怪物都有名称和初始血量。怪物还有一个额外的技能叫喷火(fire),当技能发动时造成额外更高伤害。人类有一个额外的技能叫治疗,当技能发动时可以回血(treat)。
设计一个类的继承结构来表示这个游戏中的角色和怪物,要求人类初始血量为 100,每次攻击造成 5 点伤害,人类技能治疗 10 点血量;怪物初始血量是 50 点,每次反击造成 10 点伤害,怪物技能造成 20点伤害。
【注】原创内容,如转载请务必附带原文链接
【分析】通过描述我们可以发现,人类和怪物都有一个共同特点,那就是具备名称和初始血量,所以我们可以构造一个父类,来定义这两个角色的共同特征,然后再通过创建两个子类分别代表人类和怪兽,在子类中定义他们的各自特点。
# 创建人类和怪物共有的角色类
class
Character
(): # 初始化定义名称、血量、技能使用次数
def
__init__
(self
,name,health,skill_count):self
.name = nameself
.health = healthself
.skill_count = skill_countprint
(f'已经创建了名为
{self
.name}的角色,该角色的初始血量为
{self
.health},可以使用
{self
.skill_count}次技能'
)# 定义一个描述函数,用来描述角色当前的信息
def
describe
(self
):print
(f'
{self
.name}目前的血量为
{self
.health},还可以使用
{self
.skill_count}次技能
')# 创建人类,继承来自角色类的描述函数
class
Human
(Character): # 初始化定义名称、血量、技能使用次数
def
__init__
(self
,name,health,skill_count):super()
.__init__
(name,health,skill_count)self
.skill_count = skill_count # 定义人类进攻怪物的函数
def
attack
(self
,monster):10
print
(f'{self
.name}对
{monster.name}造成了
{damage}点伤害!'
)if
monster.health <= 0
:print
(f'
{monster.name}被击败!
{self.name}取得胜利!'
) # 定义人类释放治疗技能的函数
def
treat
(self
):20
if
self
.skill_count > 0
:self
.skill_count -= 1
self
.health += increase_healthprint
(f"
{self
.name}对自己发动了'治疗技能'! 自己的血量提升了
{increase_health}点,
{self
.name}的技能使用次数还剩
{self
.skill_count}次!"
)else
:print
(f"您已经没有技能使用次数了!"
)# 创建怪物类,继承来自角色类的描述函数
class
Monster
(Character): # 初始化定义名称、血量、技能使用次数
def
_init__
(self
,name,health,skill_count):super()
.__init__
(name,health,skill_count)self
.skill_count = skill_count # 定义怪物进攻人类的函数
def
attack
(self
,human):15
print
(f'{self
.name}对
{human.name}造成了
{damage}点伤害!'
)if
human.health <= 0
:print
(f'
{human.name}被击败!
{self.name}取得胜利!'
) # 定义怪物释放喷火技能的函数
def
fire
(self
,human):30
if
self
.skill_count > 0
:self
.skill_count -= 1
print
(f"
{self
.name}对
{human.name}发动了'喷火技能'! 造成了
{fire_damage}点伤害,
{self
.name}的技能使用次数还剩
{self
.skill_count}次!"
)if
human.health <= 0
:print
(f'
{human.name}被击败!
{self.name}取得胜利!'
)else
:print
(f"
{human.name}发动了'喷火技能'!但是已经没有技能使用次数了!释放失败!"
)上面的代码定义了两个类,分别是角色类、人类和怪物类,角色类的作用是用来描述角色当前的血量和技能使用次数,人类和怪物类会继承角色类并具备描述功能,来看一下运行的效果:
# 创建角色和怪物的实例
Human
("骑士"
,100
,3
)Monster
("恶龙"
,50
,2
)# 人类进攻!怪物进攻!
"--------------【第一回合】---------------"
)attack
(monster)describe
()attack
(human)describe
()# 人类发动治疗技能!怪物发动喷火技能!
"--------------【第二回合】---------------"
)treat
()describe
()fire
(human)describe
()# 人类进攻!怪物发动喷火技能!
"--------------【第三回合】---------------"
)attack
(monster)describe
()fire
(human)describe
()# 人类进攻!怪物进攻!
"--------------【第四回合】---------------"
)attack
(monster)describe
()attack
(human)describe
()# 人类进攻!怪物发动喷火技能!
"--------------【第五回合】---------------"
)attack
(monster)describe
()fire
(human)describe
()# 人类进攻!
"--------------【第六回合】---------------"
)attack
(monster)describe
()运行代码,结果如下:
已经创建了名为骑士的角色,该角色的初始血量为100,可以使用3次技能
已经创建了名为恶龙的角色,该角色的初始血量为50,可以使用2次技能
--------------【第一回合】---------------
骑士对恶龙造成了10点伤害!
恶龙目前的血量为40,还可以使用2次技能
恶龙对骑士造成了15点伤害!
骑士目前的血量为85,还可以使用3次技能
--------------【第二回合】---------------
骑士对自己发动了'治疗技能'! 自己的血量提升了20点,骑士的技能使用次数还剩2次!
骑士目前的血量为105,还可以使用2次技能
恶龙对骑士发动了'喷火技能'! 造成了30点伤害,恶龙的技能使用次数还剩1次!
骑士目前的血量为75,还可以使用2次技能
--------------【第三回合】---------------
骑士对恶龙造成了10点伤害!
恶龙目前的血量为30,还可以使用1次技能
恶龙对骑士发动了'喷火技能'! 造成了30点伤害,恶龙的技能使用次数还剩0次!
骑士目前的血量为45,还可以使用2次技能
--------------【第四回合】---------------
骑士对恶龙造成了10点伤害!
恶龙目前的血量为20,还可以使用0次技能
恶龙对骑士造成了15点伤害!
骑士目前的血量为30,还可以使用2次技能
--------------【第五回合】---------------
骑士对恶龙造成了10点伤害!
恶龙目前的血量为10,还可以使用0次技能
骑士发动了'喷火技能'!但是已经没有技能使用次数了!释放失败!
骑士目前的血量为30,还可以使用2次技能
--------------【第六回合】---------------
骑士对恶龙造成了10点伤害!
恶龙被击败! 骑士取得胜利!
恶龙目前的血量为0,还可以使用0次技能
关注公众号【牧旗教程】,回复“更多例题”,获取更多题型进行训练~
您的打赏将帮助维护网站服务器的正常运营,并为作者的后续更新提供更多的动力。
Copyright © 2013-2023 Muqi Course. All Rights Reserved. 牧旗教程 版权所有 京ICP备2023029281号