零基础入门Python·面向对象编程篇(下)重构所有棋子类与深化OOP概念
在本篇文章中,我们将深入Python的面向对象编程(OOP),通过设计独特的游戏英雄来探索类的继承、多态性和魔法方法等高级特性。从零开始,我们不仅将学习如何构建复杂的对象,还会掌握如何将大型项目进行模块化设计,提升代码的整洁度和可维护性。无论你是编程新手还是希望提升自己的OOP技能,这篇文章都将为你提供清晰的指南和丰富的实践机会。让我们一起,用代码构建出属于自己的英雄世界。
上期作业
设计英雄类:燕赤霞
燕赤霞,源自中国古典奇幻小说《聊斋志异》,是一位正义的剑客,以剑术高强、行侠仗义著称。在游戏中,我们将其设计为一位中距离攻击的英雄,擅长快速击破敌人的防御。
英雄基本属性
- 名字:燕赤霞
- 攻击力:90
- 生命值:800
- 防御力:70
- 速度:30
- 品质:稀有(Rare)
- 羁绊:剑士、义侠
- 价格:3
特殊技能:剑影连环
燕赤霞的特殊技能是“剑影连环”,当使用时,可以对单个敌人进行多次快速攻击,每次攻击有小概率使敌人短暂眩晕。
Python类实现
class YanChixia(Hero):
"""
燕赤霞英雄类,继承自Hero类。
"""
def __init__(self):
super().__init__(name="燕赤霞", attack=90, health=800, defense=70, speed=30, quality="Rare", bond=["剑士", "义侠"], price=3)
self.skill = "剑影连环"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},对敌人进行快速多次攻击,有概率使敌人眩晕!")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
英雄设计:塔姆·肯巴
塔姆·肯巴,游戏中的一个神秘角色,以其强大的生存能力和保护盟友的能力著称。在战场上,他能够吸收大量伤害,并在关键时刻治疗自己,保持战斗力。
英雄基本属性
- 名字:塔姆·肯巴
- 攻击力:70
- 生命值:900
- 防御力:80
- 速度:20
- 品质:史诗(Epic)
- 羁绊:守护者、吞噬者
- 价格:5
特殊技能:生命回复
塔姆·肯巴的特殊技能是“生命回复”,每当场上有棋子阵亡时,塔姆·肯巴能够回复一定比例的生命值,这使得他在持久战中变得异常难以对付。
class TamKenba(Hero):
def __init__(self):
super().__init__(name="塔姆·肯巴", attack=70, health=900, defense=80, speed=20, quality="Epic", bond=["守护者", "吞噬者"], price=5)
self.skill = "生命回复"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},每当有棋子阵亡时回复生命值。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
英雄设计:塔姆·肯流
塔姆·肯流,一个敏捷而神秘的角色,擅长利用其独特的能力在战场上穿梭,躲避敌人的攻击。
英雄基本属性
- 名字:塔姆·肯流
- 攻击力:75
- 生命值:850
- 防御力:75
- 速度:25
- 品质:史诗(Epic)
- 羁绊:流浪者、幸存者
- 价格:5
特殊技能:闪避
塔姆·肯流的特殊技能是“闪避”,他有着一定几率避开敌人的非锁定攻击,包括普通攻击和技能攻击。这使得他在面对强大的敌人时,能够有效地减少受到的伤害。
class TamKenliu(Hero):
def __init__(self):
super().__init__(name="塔姆·肯流", attack=75, health=850, defense=75, speed=25, quality="Epic", bond=["流浪者", "幸存者"], price=5)
self.skill = "闪避"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},收到非锁定攻击时有一定几率闪避。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
英雄设计:赵云
赵云,三国时期的一位勇猛将领,以一身是胆的勇气和非凡的战斗技巧闻名于世。在游戏中,他以其惊人的冲锋能力和连续战斗的能力著称。
英雄基本属性
- 名字:赵云
- 攻击力:85
- 生命值:800
- 防御力:60
- 速度:40
- 品质:稀有(Rare)
- 羁绊:龙骑士、勇士
- 价格:3
特殊技能:龙胆冲阵
赵云的特殊技能是“龙胆冲阵”,每次使用技能后,他的攻击力会提升,这个效果可以叠加多次,最多七次。这使得赵云在持续战斗中变得越来越强大。
class ZhaoYun(Hero):
def __init__(self):
super().__init__(name="赵云", attack=85, health=800, defense=60, speed=40, quality="Rare", bond=["龙骑士", "勇士"], price=3)
self.skill = "龙胆冲阵"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},每次使用技能后攻击力提升,最多叠加七次。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
英雄设计:苏秦
苏秦,战国时期的著名外交家,以其出色的谋略和策略,成功实施了合纵连横的外交策略。在游戏中,他以其独特的辅助能力和羁绊增强技能著称。
英雄基本属性
- 名字:苏秦
- 攻击力:60
- 生命值:700
- 防御力:50
- 速度:30
- 品质:普通(Common)
- 羁绊:谋士、外交家
- 价格:1
特殊技能:六国相印
苏秦的特殊技能是“六国相印”,他能够通过外交手段增强己方所有羁绊的效果,使己方在战斗中获得更多的战略优势。
class SuQin(Hero):
def __init__(self):
super().__init__(name="苏秦", attack=60, health=700, defense=50, speed=30, quality="Common", bond=["谋士", "外交家"], price=1)
self.skill = "六国相印"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},通过外交手段增强己方羁绊效果。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
英雄设计:亚索
亚索,一个被风神所眷顾的剑士,以其高超的剑术和敏捷的身手著称。在游戏中,他能够迅速穿梭于战场,为敌人带来致命的打击。
英雄基本属性
- 名字:亚索
- 攻击力:95
- 生命值:750
- 防御力:65
- 速度:35
- 品质:史诗(Epic)
- 羁绊:剑士、狂风
- 价格:4
特殊技能:踏浪而行
亚索的特殊技能是“踏浪而行”,在使用技能后,他能够吸引所有敌人的注意,同时增加自身的防御力。这使得亚索在团队中扮演着重要的抗伤害角色。
class Yasuo(Hero):
def __init__(self):
super().__init__(name="亚索", attack=95, health=750, defense=65, speed=35, quality="Epic", bond=["剑士", "狂风"], price=4)
self.skill = "踏浪而行"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},释放后吸引所有敌人的注意,增加自身防御力。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
重构项目结构
新的项目结构长这样
/零基础入门Python/code/day2
├── champion
| ├── __init__.py
| ├── base.py
| └── hero.py
├── main.py
├── player.py
└── shop.py
base.py
这个文件现在是棋子类Champion
# champion/base.py
from typing import List
class Champion:
"""代表游戏中所有角色的基类。
属性:
name (str): 角色名称。
attack (int): 攻击力。
health (int): 生命值。
defense (int): 防御力。
speed (int): 速度。
attack_range (int): 攻击距离。
attack_speed (int): 攻击速度。
"""
def __init__(self, name: str, attack: int, health: int, defense: int, speed: int, attack_range: int = 1, attack_speed: int = 1) -> None:
"""初始化角色的基本属性以及攻击距离和攻击速度。"""
self.name: str = name
self.attack: int = attack
self.health: int = health
self.defense: int = defense
self.speed: int = speed
self.attack_range: int = attack_range
self.attack_speed: int = attack_speed
def take_damage(self, amount: int) -> None:
"""角色受到伤害并减少生命值,如果生命值小于等于0,则调用die方法。
参数:
amount (int): 受到的伤害量。
"""
self.health -= amount
if self.health <= 0:
self.die()
def die(self) -> None:
"""角色死亡时的处理方法。"""
print(f"{self.name} has died.")
def display_info(self) -> None:
"""显示角色的所有基本信息。"""
info = (
f"Name: {self.name}\n"
f"Attack: {self.attack}\n"
f"Health: {self.health}\n"
f"Defense: {self.defense}\n"
f"Speed: {self.speed}\n"
f"Attack Range: {self.attack_range}\n"
f"Attack Speed: {self.attack_speed}"
)
print(info)
hero.py
这个文件是所有英雄,现在长这样
from .base import Champion
from typing import List
class Hero(Champion):
"""代表游戏中的英雄,继承自Champion类,并添加了英雄特有的属性,包括价格。
属性:
level (int): 英雄的等级。
quality (str): 英雄的品质。
bond (List[str]): 英雄的羁绊列表。
price (int): 购买或升级英雄所需的价格。
"""
def __init__(self, name: str, attack: int, health: int, defense: int, speed: int, level: int = 1, quality: str = "Common", bond: List[str] = None, price: int = 100) -> None:
"""初始化英雄的基本属性以及英雄特有的属性,包括价格。"""
super().__init__(name, attack, health, defense, speed)
self.level = level
self.quality = quality
self.bond = bond if bond is not None else []
self.price = price
def level_up(self) -> None:
"""提升英雄的等级,并相应增加价格。"""
self.level += 1
print(f"{self.name} has leveled up to level {self.level}! New price: {self.price}")
def display_info(self) -> None:
"""显示英雄的所有信息,包括继承自Champion的属性和英雄特有的属性,以及价格。"""
super().display_info()
extra_info = (
f"Level: {self.level}\n"
f"Quality: {self.quality}\n"
f"Bond: {', '.join(self.bond) if self.bond else 'None'}\n"
f"Price: {self.price}"
)
print(extra_info)
# ----------------------------------------想着把上面的类都复制进来啊----------------------------------------
class Yasuo(Hero):
def __init__(self):
super().__init__(name="亚索", attack=95, health=750, defense=65, speed=35, quality="Epic", bond=["剑士", "狂风"], price=4)
self.skill = "踏浪而行"
def use_skill(self):
print(f"{self.name} 使用了特殊技能:{self.skill},释放后吸引所有敌人的注意,增加自身防御力。")
def display_info(self):
super().display_info()
print(f"Special Skill: {self.skill}")
更新main.py
# main.py
from shop import Shop
from player import Player
# 导入Hero类及其派生类
from champion.hero import YanChixia, TamKenba, TamKenliu, ZhaoYun, SuQin, Yasuo, SunWukong
def main():
"""
游戏的主入口。初始化游戏环境,并进入游戏的主循环。
"""
# 定义英雄类列表,而不是英雄实例列表
hero_classes = [YanChixia, TamKenba, TamKenliu, ZhaoYun, SuQin, Yasuo, SunWukong]
# 创建商店实例,传入英雄类列表
shop = Shop(hero_classes)
player = Player()
# 游戏主循环
while True:
shop.refresh_store() # 刷新商店,这时会从英雄类列表中随机选择并创建英雄实例
print("可购买的英雄:")
shop.show_champions() # 显示当前商店中的英雄
choice = input("想购买哪位英雄?(输入名称或'退出'结束游戏)")
if choice.lower() == '退出':
break
found_champion = None
for champ in shop.store_champions:
if champ.name.lower() == choice.lower():
found_champion = champ
break
if found_champion:
player.buy_champion(found_champion, shop)
else:
print("商店中找不到该英雄。")
print(f"剩余金币: {player.gold}")
if __name__ == "__main__":
main()
运行一下看看效果
# python main.py
/零基础入门Python/code/day2$ python3 main.py
可购买的英雄:
<class 'champion.hero.YanChixia'>
<class 'champion.hero.TamKenba'>
<class 'champion.hero.Yasuo'>想购买哪位英雄?(输入名称或'退出'结束游戏)
歪?魔法方法 __str__
在Python中,魔法方法(也称为特殊方法或双下方法)是一类以双下划线(__
)开头和结尾的方法,它们提供了一种方式来实现和修改对象的内置行为。这些方法允许开发者对自定义对象使用内置的Python操作,比如算术运算、长度检查、字符串表示等。魔法方法使得自定义对象能够以一种非常自然和Pythonic的方式与Python的内置类型和操作集成。
__str__
是一个非常常用的魔法方法,它定义了当对象需要被转换成字符串时的行为,比如使用str()
函数或者print()
函数时。这个方法应该返回一个代表对象的字符串。通常,这个字符串对于用户是友好的,提供了关于对象的有意义的信息。
简单来说就是,print打印出来什么是这个魔法方法说了算的
而刷新商店列表刚好就是用print打印的
def show_champions(self):
for champ in self.store_champions:
print(champ)
找到问题了,那我们在英雄类中添加一下这个方法
class Hero(Champion):
"""代表游戏中的英雄,继承自Champion类,并添加了英雄特有的属性,包括价格。
属性:
level (int): 英雄的等级。
quality (str): 英雄的品质。
bond (List[str]): 英雄的羁绊列表。
price (int): 购买或升级英雄所需的价格。
"""
# ---------------------------原来的方法不变---------------------
def __str__(self):
bonds_str = ", ".join(self.bond)
return f" -- 英雄: {self.name}, 售价: {self.price}, 羁绊: {bonds_str}"
再修改一下商店的刷新方法
# shop.py
import random # 导入random模块,以便进行随机操作
class Shop:
"""
管理游戏中的商店,包括棋子的展示和刷新。
属性:
- champions_classes (list): 可购买的棋子列表。
- store_champions (list): 当前商店中展示的棋子列表。
方法:
- refresh_store: 随机从champions_classes中选择3个棋子展示在商店中。
- show_champions: 打印当前商店中可购买的棋子。
"""
def __init__(self, champions_classes):
self.champions_classes = champions_classes # ---------------- 存储棋子类的列表,而非实例
self.store_champions = [] # 当前商店中展示的棋子实例列表
def refresh_store(self):
selected_classes = random.sample(self.champions_classes, 3) # ---------------------------新增的
self.store_champions = [cls() for cls in selected_classes] # ---------------------------新增的
def show_champions(self):
for champ in self.store_champions:
print(champ)
- 如何存储可供选择的棋子(英雄):我们选择存储棋子的类而不是实例。这样做的原因是,每次我们想要"刷新"商店展示的棋子时,我们希望这些棋子是新创建的实例,以避免任何先前状态的干扰。
- 如何随机选择棋子来展示:我们使用
random.sample
方法从所有可选的棋子类中随机选择一定数量(本例中为3个)来展示。这样每次刷新商店时都会给玩家一种新的选择。- 如何展示这些选择:我们通过实例化选中的棋子类,并将这些实例存储在一个列表中来展示它们。玩家可以查看这个列表,选择他们想要购买的棋子。
列表推导式
列表推导式是Python中一个强大的功能,它提供了一种简洁的方法来创建列表。基本语法如下:
[expression for item in iterable if condition]
expression
是当前项上的操作,结果会被添加到新列表中。item
是从iterable
中遍历出的对象。iterable
是你想要遍历的对象集合。if condition
是一个可选项,只有满足条件的项才会被处理。在
Shop
类的refresh_store
方法中使用的列表推导式如下:self.store_champions = [cls() for cls in selected_classes]
这里的逻辑是:
selected_classes
是一个包含棋子类的列表。- 对于
selected_classes
列表中的每一个类(cls
),我们调用它(cls()
)来创建一个该类的实例。- 这些实例被收集到一个新的列表中,这个新列表赋值给
self.store_champions
。这个列表推导式等同于以下的循环形式,但更加简洁:
self.store_champions = [] for cls in selected_classes: self.store_champions.append(cls())
列表推导式不仅使代码更简洁,还可以提高代码的可读性(一旦习惯了这种语法),并且在很多情况下,它的执行效率比相应的循环形式要高。
player.py,售价换了变量名称
# player.py
# 该模块是buy.py的升级版本,后续游戏中玩家的相关封装都在这儿
class Player:
"""
代表游戏中的玩家,管理玩家的金币和购买行为。
属性:
- gold (int): 玩家拥有的金币数量。
方法:
- buy_champion: 尝试使用金币购买指定的棋子。
"""
def __init__(self, gold=5):
self.gold = gold
def buy_champion(self, champion, shop):
if champion in shop.store_champions and self.gold >= champion.price:
self.gold -= champion.price
shop.store_champions.remove(champion)
print(f"已购买{champion}。")
else:
print("无法购买该英雄。")
完整的main.py,初始金钱增加到了20
# main.py
from shop import Shop
from player import Player
# 从champion.hero模块导入Hero类及其所有派生类
from champion.hero import YanChixia, TamKenba, TamKenliu, ZhaoYun, SuQin, Yasuo, SunWukong
def main():
"""
游戏的主入口点。此函数负责初始化游戏环境,包括商店和玩家,并进入游戏的主循环。
在游戏主循环中,玩家可以查看当前商店中可购买的英雄,并决定是否购买。
"""
# 初始化一个包含所有可购买英雄类的列表。注意这里存储的是类引用,而不是实例。
hero_classes = [YanChixia, TamKenba, TamKenliu, ZhaoYun, SuQin, Yasuo, SunWukong]
# 创建商店实例,初始化时传入英雄类列表。
shop = Shop(hero_classes)
# 创建玩家实例,假设初始金币为10。
player = Player(10)
# 游戏的主循环开始
while True:
shop.refresh_store() # 刷新商店,随机选择英雄类并创建新实例展示在商店中。
print("可购买的英雄:")
shop.show_champions() # 显示当前商店中可购买的英雄列表。
# 提示玩家输入想购买的英雄名称或输入'退出'来结束游戏。
choice = input("想购买哪位英雄?(输入名称或'退出'结束游戏)")
if choice.lower() == '退出':
break # 如果玩家选择退出,则跳出游戏主循环,游戏结束。
# 在商店中查找玩家想要购买的英雄。
found_champion = None
for champ in shop.store_champions:
if champ.name.lower() == choice.lower():
found_champion = champ
break
# 如果找到了玩家想要的英雄,则尝试购买。
if found_champion:
player.buy_champion(found_champion, shop)
else:
print("商店中找不到该英雄。")
# 打印玩家剩余的金币数量。
print(f"剩余金币: {player.gold}")
# 如果此脚本作为主程序运行,则调用main函数。
if __name__ == "__main__":
main()
运行一下看看效果
python main.py
/零基础入门Python/code/day2$ python3 main.py
可购买的英雄:
-- 英雄: 孙悟空, 售价: 5, 羁绊: 西游记, 神仙
-- 英雄: 赵云, 售价: 3, 羁绊: 龙骑士, 勇士
-- 英雄: 塔姆·肯流, 售价: 5, 羁绊: 流浪者, 幸存者
想购买哪位英雄?(输入名称或'退出'结束游戏)孙悟空
已购买 -- 英雄: 孙悟空, 售价: 5, 羁绊: 西游记, 神仙。
剩余金币: 15
可购买的英雄:
-- 英雄: 塔姆·肯巴, 售价: 5, 羁绊: 守护者, 吞噬者
-- 英雄: 孙悟空, 售价: 5, 羁绊: 西游记, 神仙
-- 英雄: 亚索, 售价: 4, 羁绊: 剑士, 狂风
想购买哪位英雄?(输入名称或'退出'结束游戏)亚索
已购买 -- 英雄: 亚索, 售价: 4, 羁绊: 剑士, 狂风。
剩余金币: 11
可购买的英雄:
-- 英雄: 亚索, 售价: 4, 羁绊: 剑士, 狂风
-- 英雄: 燕赤霞, 售价: 3, 羁绊: 剑士, 隐侠, 末法道子
-- 英雄: 塔姆·肯流, 售价: 5, 羁绊: 流浪者, 幸存者
想购买哪位英雄?(输入名称或'退出'结束游戏)燕赤霞
已购买 -- 英雄: 燕赤霞, 售价: 3, 羁绊: 剑士, 隐侠, 末法道子。
剩余金币: 8
可购买的英雄:
-- 英雄: 燕赤霞, 售价: 3, 羁绊: 剑士, 隐侠, 末法道子
-- 英雄: 亚索, 售价: 4, 羁绊: 剑士, 狂风
-- 英雄: 赵云, 售价: 3, 羁绊: 龙骑士, 勇士
想购买哪位英雄?(输入名称或'退出'结束游戏)亚索
已购买 -- 英雄: 亚索, 售价: 4, 羁绊: 剑士, 狂风。
剩余金币: 4
可购买的英雄:
-- 英雄: 塔姆·肯巴, 售价: 5, 羁绊: 守护者, 吞噬者
-- 英雄: 燕赤霞, 售价: 3, 羁绊: 剑士, 隐侠, 末法道子
-- 英雄: 塔姆·肯流, 售价: 5, 羁绊: 流浪者, 幸存者
想购买哪位英雄?(输入名称或'退出'结束游戏)
总结
文本中包含了多个Python高级特性和面向对象编程的核心概念。以下是一些关键的知识点总结:
类的继承和多态性
- 继承:通过继承,子类(如
Hero
)可以继承父类(如Champion
)的方法和属性,同时还可以添加或修改特定的属性和方法。这有助于代码的复用和扩展。 - 多态性:通过继承实现的多态性允许我们在不同的子类中定义具有相同名称的方法(如
display_info
),但可以实现不同的功能。这使得我们可以在不同的情况下调用同一个接口,执行不同的操作。
魔法方法
__str__
方法:定义了对象的字符串表示形式。当使用print()
函数打印对象或使用str()
函数转换对象时,Python会自动调用该对象的__str__
方法。在本项目中,通过定义英雄类的__str__
方法,我们可以控制英雄对象被打印时的格式,使输出更加友好和信息丰富。
列表推导式
- 列表推导式:提供了一种简洁的方法来创建列表。它是基于已有列表(或任何可迭代对象)创建新列表的一行式方法。在本项目中,列表推导式被用于从英雄类列表中创建英雄实例列表,展示了其在生成新列表时的便捷性和效率。
模块化设计
- 模块化:将不同的功能分割到不同的模块(文件)中,有助于代码的组织和管理。例如,将英雄类定义在
hero.py
中,而将商店逻辑放在shop.py
中。这样的设计不仅使代码更加清晰,也便于维护和扩展。
类型注解
- 类型注解:Python的类型注解功能可以在代码中添加变量的预期类型。这不会影响Python代码的运行,但可以帮助开发者理解函数或方法应该接收什么类型的参数,以及它们将返回什么类型的值。类型注解提高了代码的可读性和可维护性。
转载自:https://juejin.cn/post/7362847673773604873