紧接着是下面一题。
第七十题:子类重写父类方法
1. 原始版本:儿子借老爹的本事,再自己改口
#定义一个父类Vehicle,包含一个方法describe,用于描述车辆具体信息。
#定义一个子类Car,继承自vehicle,并重写describe方法,以提供关于汽车的特定信息
#定义父类
class Vehicle:
"""
定义一个父类Vehicle,包含一个方法describe,用于描述车辆具体信息。
"""
#初始化车辆对象
def __init__(self,make,model):
self.make = make
self.model = model
#描述车辆信息
def describe(self):
return f"这辆车的制造商是 {self.make},型号是 {self.model}。"
#定义子类,继承自父类
class Car(Vehicle):
"""
定义一个子类Car,继承自vehicle,并重写describe方法,以提供关于汽车的特定信息
"""
def __init__(self, make, model, year):
#调用父类的__init__,初始化制造商与型号
super().__init__(make, model)
#再初始化子类特有的属性
self.year = year
#重写父类方法
def describe(self):
"""
重写父类方法,提供关于汽车的特定信息
return: str
"""
return f"这辆车的年份是 {self.year}。"
#示例使用
car = Car("丰田", "卡罗拉", 2024)
#描述汽车基本信息
print(car.describe())
#调用父类的describe,展示制造商与型号信息
print(Vehicle.describe(car))
思路:这题第一次把类串成了父子关系。父类Vehicle存make和model,describe把它们描述出来。子类Car写个class Car(Vehicle),就白捡了老爹的全部家当——这就是继承。
子类比父类多了个year,但make、model没必要重写一遍。所以在子类的__init__里先super().__init__(make, model)把这两个交给老爹初始化,自己只管多出来的year。能继承就别重复造轮子。
重写就是子类重新定义同名方法,把describe换成汽车专属的。最后印两次:car.describe()走子类重写版,Vehicle.describe(car)显式点名要父类版本。同一个对象,两个describe各说各话,继承关系一下就落地了。
局限:只造一辆车、数据写死;而且子类describe只说了年份,把制造商和型号丢了,重写反而越写越窄。
2. 优化版:先选类别,再挑车,查完还能接着查
#定义一个父类Vehicle,包含一个方法describe,用于描述车辆具体信息。
#定义一个子类Car,继承自Vehicle,并重写describe方法,以提供关于汽车的特定信息。
#(优化版:两级选择——先选车辆类别,再选具体车辆查询,查完询问是否再次查询或退出)
#定义父类
class Vehicle:
"""
定义一个父类Vehicle,包含一个方法describe,用于描述车辆具体信息。
"""
#初始化车辆对象
def __init__(self, make, model):
self.make = make
self.model = model
#描述车辆信息
def describe(self):
return f"这辆车的制造商是 {self.make},型号是 {self.model}。"
#定义子类,继承自父类
class Car(Vehicle):
"""
定义一个子类Car,继承自Vehicle,并重写describe方法,以提供关于汽车的特定信息。
"""
def __init__(self, make, model, year):
#调用父类的__init__,初始化制造商与型号
super().__init__(make, model)
#再初始化子类特有的属性
self.year = year
#重写父类方法
def describe(self):
"""
重写父类方法,提供关于汽车的特定信息。
:return: str
"""
return f"这是一辆 {self.year} 年产的 {self.make} {self.model}。"
#按类别组织可查询的车辆:键是类别名,值是该类别下的车辆对象列表
#目前子类只有 Car(汽车),因此只提供“汽车”这一类别
vehicle_types = {
"汽车": [
Car("丰田", "卡罗拉", 2024),
Car("比亚迪", "汉", 2023),
Car("特斯拉", "Model 3", 2025),
],
}
#辅助函数:读取一个范围内的整数编号(带输入校验,两级菜单复用)
def read_index(prompt, count) -> int:
"""
反复提示用户输入,直到选择一个 1 ~ count 范围内的合法编号为止。
:param prompt: 输入提示信息
:param count: 可选择的数量上限
:return: 对应的列表索引(int)
"""
index = 0
valid = False
#使用标志位控制循环,循环外统一 return,避免 PyCharm 将 while True 误判为可能返回 None
while not valid:
try:
choice = int(input(prompt))
except ValueError:
print("输入无效,请输入一个数字。")
continue
#范围校验独立于 try/except,避免合法数字绕过边界判断
if 1 <= choice <= count:
index = choice - 1
valid = True
else:
print(f"输入超出范围,请输入 1 ~ {count} 之间的数字。")
return index
#辅助函数:展示某类别下的车辆列表
def show_vehicles(vehicles):
"""
打印该类别下所有车辆的编号与基本信息。
:param vehicles: 车辆对象列表
"""
for i, vehicle in enumerate(vehicles, start=1):
#子类汽车额外显示年份
print(f" {i}. {vehicle.make} {vehicle.model}({vehicle.year} 年)")
#主程序
while True:
#第一级:展示可查询的车辆类别,让用户先选择类别
type_names = list(vehicle_types.keys())
print("\n" + "=" * 40)
print("欢迎使用车辆信息查询系统,请选择要查询的车辆类别:")
print("=" * 40)
for i, name in enumerate(type_names, start=1):
count = len(vehicle_types[name])
print(f" {i}. {name}(可查询 {count} 辆)")
print("-" * 40)
type_index = read_index("请输入类别编号:", len(type_names))
selected_type = type_names[type_index]
vehicles = vehicle_types[selected_type]
#第二级:进入所选类别,展示该类别下的车辆并选择一辆查询
print(f"\n【{selected_type}】现有以下车辆可查询:")
show_vehicles(vehicles)
car_index = read_index("请输入要查询的车辆编号:", len(vehicles))
selected = vehicles[car_index]
#多态调用:子类对象执行重写后的 describe
print("查询结果:", selected.describe())
#查询完成后询问用户是再次查询还是退出程序
again = input("是否再次查询信息?(y=继续 / 其他=退出):").strip().lower()
if again != "y":
print("感谢使用,查询系统已退出。")
break
思路:继承结构没动,升级的是怎么用这些对象。子类describe改成这是一辆 {year} 年产的 {make} {model},既用继承来的make/model,又加自己的year,重写不再丢信息。
真正的变化在外层交互。单辆车太寒碜,我建了个vehicle_types字典:键是类别,值是一串Car对象。这就是“列表装对象”的升级版——字典套列表,天然适合“先分类、再在类里挑”的两级结构。
两级菜单都要“输一个 1~N 的编号”,我不写两遍,抽成read_index(prompt, count),范围上限当参数传进去,复用给类别层和车辆层。这里用了标志位循环while not valid,跳出后统一return,绕开 PyCharm 对while True可能返回None的误判。
最后走一遍:列类别 → 选类别 → 列该车队 → 选车 →selected.describe()。这句正是多态的实战:不管selected是哪辆车,统一调describe(),各自执行自己那版重写。父类子类一套接口、多种表现,这题算是把继承主题圆上了。