Day.10
2020.02.28
今天同样是继续学习python的面向对象编程,内容主要是面向对象编程的继承和多态。这里先简单说一下概念,继承就像是儿子继承爸爸妈妈的长相一样,子类将会继承父类的属性和方法,当然,也可以添加子类自己特殊的属性和方法,也可以改写父类的方法,多个不同的子类继承同一父类,就是多态的表现。
1.面向对象进阶
i.装饰器@property
首先是装饰器@property。在上文提到,python类中的属性存在访问权限的问题,在要保护的属性名称前面加单一下划线是一种方法,虽然比较方便但是其实并不安全。如果想要对属性的访问既方便又安全,就可以使用@property包装器来包装getter和setter方法。
其中,getter方法用于将属性设为只读,而setter方法用于将属性设为可读写。使用方法如以下实例所示:
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
很显然,name是只读属性,而age是可以进行读写操作的(虽然age定义了getter方法,但是同时也定义了setter方法,可以对其进行外来参数的赋值)。
ii.__slots__魔法
其次是魔法__slots__。因为python是一门动态的语言,它允许在程序运行时,给对象绑定新的属性或方法,或者给对象解绑一些属性和方法,所以在这个过程中,我们遇到的问题是,如何限定自定义对象只能绑定某些属性,那么这个时候就需要用__slots__。需要注意的是,__slots__只对当前类的对象有效,而对子类的对象无效。具体实现方法如下:
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
iii.静态方法和类方法
第三个是静态方法和类方法。首先我列个表格总结一下实例方法、静态方法和类方法的区别:
方法 | 定义 | 调用 |
---|---|---|
实例方法 | 第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法) | 只能由实例对象调用 |
类方法 | 使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法) | 实例对象和类对象都可以调用 |
静态方法 | 使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法 | 实例对象和类对象都可以调用 |
①类方法:原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。
②静态方法:静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护(比如骆昊百天项目中的例子:判断是否能创建三角形类对象的函数Triangle.is_valid(a, b, c),这是一个单纯的函数,Triangle是类名,并不是任何类对象或实例对象)。
关于这三种方法的详细比较和解释,我认为有一篇博文讲的是非常到位的:
Python实例方法、类方法、静态方法的区别与作用
iv.类之间的关系
虽然类和类之间在语法上只有继承,但是类与类之间可以分为三种关系:
- is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
- use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
如果以上的内容不好理解的话,可以看一下下面这张图:
这张图通过具体的例子把三种关系都写了出来:司机Driver继承于人类Person的大类,司机Driver类又和驾照License类关联,但是要注意的是,司机和驾照是同级别的关系,比如上文所说的部门和员工。另外,汽车Vehicle和引擎Engine也是关联关系,但这两者是整体和部分的关系,所以也叫聚合。司机需要开车,因此司机和汽车的关系是依赖关系。同样的,汽车Vehicle大类也可以继承出普通车子Car和卡车Truck的小类。
继承和多态就不再多说,唯一需要注意的一点是:多继承的时候,如果父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索(即方法在子类中未找到时,从左到右查找父类中是否包含方法)。
2.综合案例练习
GitHub上在这一节的最后是三个综合案例,主要是灵活运用类的继承和多态,因为网站上直接有代码给出,我就没有重复编写这三个案例。但是为了达到练习的效果,我自己编写了一个回合制打怪的实例:
#回合制打怪:
#作者:囷囷
import random
class Role:
name = ''
hp = 500
mp = 500
attack = 100
defend = 50
damage = 0
# 伤害计算公式: 伤害=自己攻击力-对手防御力
def __init__(self, name, hp, mp, attack, defend):
self.name = name
self.hp = hp
self.mp = mp
self.attack = attack
self.defend = defend
# 普通攻击:
def AtkTo(self, other):
self.damage = self.attack - other.defend
other.hp -= self.damage
class Player(Role):
exp = 0
level = 1
skill = ['普通攻击']
SkillDamage = 0
MpIsEnough = 1
def __init__(self, name, hp, mp, attack, defend, exp, level, SkillDamage):
Role.__init__(self, name, hp, mp, attack, defend)
self.exp = exp
self.level = level
self.SkillDamage = SkillDamage
# 技能攻击:
def SkillsTo(self, other):
self.SkillDamage = self.SkillDamage - other.defend
other.hp -= self.SkillDamage
# 选择技能:
def ChooseYourSkills(self, x):
if self.skill[x] == '肉蛋葱鸡':
if self.mp >= 90:
self.MpIsEnough = 1
self.mp -= 90
self.SkillDamage = 270
else:
self.MpIsEnough = 0
print('对不起!您的蓝量不足,无法再释放技能!')
elif self.skill[x] == '我夹':
if self.mp >= 50:
self.MpIsEnough = 1
self.mp -= 50
self.SkillDamage = 90
else:
self.MpIsEnough = 0
print('对不起!您的蓝量不足,无法再释放技能!')
elif self.skill[x] == '起飞':
if self.mp >= 70:
self.MpIsEnough = 1
self.mp -= 70
self.SkillDamage = 200
else:
self.MpIsEnough = 0
print('对不起!您的蓝量不足,无法再释放技能!')
elif self.skill[x] == '治疗术':
if self.mp >= 10:
self.MpIsEnough = 1
self.mp -= 10
self.SkillDamage = 0
self.hp += 100
else:
self.MpIsEnough = 0
print('对不起!您的蓝量不足,无法再释放技能!')
else:
if self.mp >= 100:
self.MpIsEnough = 1
self.mp -= 100
self.SkillDamage = 400
else:
self.MpIsEnough = 0
print('对不起!您的蓝量不足,无法再释放技能!')
class Monster(Role):
SkillDamage = 0
skill = []
MpIsEnough = 1
TempSkill = ''
def __init__(self, name, skill, hp, mp, attack, defend, SkillDamage):
Role.__init__(self, name, hp, mp, attack, defend)
self.SkillDamage = SkillDamage
self.skill = skill
def MonsterSkillsTo(self, other):
self.SkillDamage = self.SkillDamage - other.defend
if self.SkillDamage <= 0:
self.SkillDamage = 0
other.hp -= self.SkillDamage
else:
other.hp -= self.SkillDamage
def ChooseMonsterSkills(self, other):
self.TempSkill = random.choice(self.skill)
if self.TempSkill == '普通攻击':
self.SkillDamage = self.attack - other.defend
elif self.TempSkill == '车轮滚滚':
self.SkillDamage = 270
elif self.TempSkill == '我夹':
self.SkillDamage = 90
elif self.TempSkill == '大地震击':
self.SkillDamage = 200
elif self.TempSkill == '势不可挡':
self.SkillDamage = 400
elif self.TempSkill == '疏通肠道':
self.SkillDamage = 280
elif self.TempSkill == '泰坦之怒':
self.SkillDamage = 70
elif self.TempSkill == '暗流涌动':
self.SkillDamage = 175
elif self.TempSkill == '深海葱鸡':
self.SkillDamage = 80
elif self.TempSkill == '斩钢闪':
self.SkillDamage = 120
elif self.TempSkill == '风之障壁':
self.SkillDamage = 0
elif self.TempSkill == '踏前斩':
self.SkillDamage = 100
else:
self.SkillDamage = 400
def main():
NotInRound = 1
Player1 = Player('朱一飞', 1000, 1000, 100, 100, 0, 1, 0)
Monster1 = Monster('墨菲特', ['普通攻击', '车轮滚滚', '我夹', '大地震击', '势不可挡'], 1000, 0, 50, 10, 0)
Monster2 = Monster('深海泰坦', ['普通攻击', '疏通肠道', '泰坦之怒', '暗流涌动', '深海葱鸡'], 2000, 0, 10, 50, 0)
Monster3 = Monster('亚索', ['普通攻击', '斩钢闪', '风之障壁', '踏前斩', '狂风绝息斩'], 200, 0, 200, 10, 0)
MonsterList = [Monster1, Monster2, Monster3]
round = 1
datalist = ['肉蛋葱鸡', '我夹', '起飞', '千层饼', '治疗术']
y = int(input('按1进入游戏,按其他键退出游戏:'))
while True:
if y == 1 and Player1.hp > 0:
if NotInRound == 1:
NotInRound = 0
TempMonster = random.choice(MonsterList)
if TempMonster.name == '墨菲特':
TempMonster.hp = 1000
TempMonster.mp = 0
TempMonster.attack = 50
TempMonster.defend = 10
if TempMonster.name == '深海泰坦':
TempMonster.hp = 2000
TempMonster.mp = 0
TempMonster.attack = 10
TempMonster.defend = 50
if TempMonster.name == '亚索':
TempMonster.hp = 200
TempMonster.mp = 0
TempMonster.attack = 200
TempMonster.defend = 10
print('遭遇怪物%s!' % TempMonster.name)
while TempMonster.hp > 0 and Player1.hp > 0:
print('----------信息:----------')
print('玩家%s:' % Player1.name)
print('HP:%d,MP:%d,EXP:%d,攻击力:%d,防御力:%d,等级:%d' % (
Player1.hp, Player1.mp, Player1.exp, Player1.attack, Player1.defend, Player1.level))
print('怪物%s:' % TempMonster.name)
print('HP:%d,MP:%d,攻击力:%d,防御力:%d' % (
TempMonster.hp, TempMonster.mp, TempMonster.attack, TempMonster.defend))
print('----------第%d回合----------' % round)
print('技能列表:')
for m in range(0, len(Player1.skill)):
print('%d:%s ' % (m, Player1.skill[m]))
x = int(input('请按技能列表对应的数字键进行操作:'))
if x == 0:
Player1.AtkTo(TempMonster)
TempMonster.ChooseMonsterSkills(Player1)
TempMonster.MonsterSkillsTo(Player1)
print('玩家%s使用了 %s 对怪物%s造成了%d点伤害!' % (
Player1.name, Player1.skill[x], TempMonster.name, Player1.damage))
print('怪物%s使用了 %s 对玩家%s造成了%d点伤害!' % (
TempMonster.name, TempMonster.TempSkill, Player1.name, TempMonster.SkillDamage))
if x != 0:
Player1.ChooseYourSkills(x)
if Player1.MpIsEnough == 0:
print('请重新选择其他技能进行释放!')
continue
else:
if Player1.skill[x] == '治疗术':
TempMonster.ChooseMonsterSkills(Player1)
TempMonster.MonsterSkillsTo(Player1)
print('玩家%s使用了 %s 回复了%d点生命值!' % (Player1.name, Player1.skill[x], 100))
print('怪物%s使用了 %s 对玩家%s造成了%d点伤害!' % (
TempMonster.name, TempMonster.TempSkill, Player1.name, TempMonster.SkillDamage))
else:
Player1.SkillsTo(TempMonster)
TempMonster.ChooseMonsterSkills(Player1)
TempMonster.MonsterSkillsTo(Player1)
print('玩家%s使用了 %s 对怪物%s造成了%d点伤害!' % (
Player1.name, Player1.skill[x], TempMonster.name, Player1.SkillDamage))
print('怪物%s使用了 %s 对玩家%s造成了%d点伤害!' % (
TempMonster.name, TempMonster.TempSkill, Player1.name, TempMonster.SkillDamage))
if random.randint(1, 100) > 25:
if datalist:
TempSkill = random.choice(datalist)
print('怪物%s掉落了技能 %s !已自动拾取!' % (TempMonster.name, TempSkill))
Player1.skill.append(TempSkill)
datalist.remove(TempSkill)
if Player1.hp <= 0:
Player1.hp = 0
print('玩家%s已经死亡!游戏结束!' % Player1.name)
break
if TempMonster.hp <= 0:
TempMonster.hp = 0
Player1.exp += 50
if Player1.exp == 100:
Player1.level += 1
Player1.hp += 500
Player1.mp += 500
Player1.attack += 10
Player1.defend += 10
Player1.exp = 0
Player1.MpIsEnough = 1
print('恭喜你已成功击败怪物%s!' % TempMonster.name)
print('----------回合结束!----------')
round = 1
NotInRound = 1
break
round += 1
else:
print('结束游戏!')
break
if __name__ == '__main__':
main()
小结:其中的很多数据和方法并不完善,并且代码肯定也还有精简的地方。实现的思路主要是创建一个角色Role的大类,包含血量、蓝量、攻击力和防御力等基本属性,然后再以Role作为父类,继承出子类玩家Player和怪物Monster,再分别给这两类增加不同的属性和方法。最后在main()函数中以一个while循环作为游戏进程即可。
3.今日总结
自从接触了面向对象编程之后,我才真切感受到了python语言的可读性之强,即使是自己写的自定义类,在主函数中也依然能读懂各种方法的作用,对于代码的维护和修改有着很大的帮助。不足之处是还应该多尝试着应用各种方法,而不是只用实例方法,并且也要活用__slots__和@property。
同时,如果想要更进一步把面向对象练熟练透的话,我建议认真阅读一下python中所有的魔法方法使用指南:
(译)Python魔法方法指南