资讯动态

Python面向对象实验报告实战:从Vehicle类到多态与dataclass进阶

发布时间:2026/10/21 5:43:10 来源:尧图企业网站定制
简介这份Python实验报告面向正在学习面向对象程序设计的高校学生与自学者聚焦类与对象、封装、继承、多态等核心概念帮助读者通过完整实验案例理解并实践Python面向对象编程的基本原理与技巧。资源包内含1个doc文档大小约323KB内容涵盖实验目的、实验内容、核心代码与运行结果结构完整便于对照学习。文档以交通工具Vehicle类为主线讲解成员变量、构造函数、析构函数、静态变量、类方法及成员函数的定义与调用并进一步通过抽象类与Car、Airplane子类演示继承与多态的实现方式配合map函数与列表推导式展示多态调用过程。目前已有3138人学习下载适合需要完成课程实验、巩固面向对象基础或准备相关练习的读者参考可帮助读者快速掌握类的创建、对象实例化及继承多态的实际运用。1. 从一份 Python 实验报告说起面向对象到底该怎么落地很多人第一次接触 Python 面向对象程序设计都是从一份实验报告开始的。你手里这份Python实验一 面向对象程序设计实验.doc就是典型场景它把类、对象、封装、继承、多态、构造函数、析构函数、静态变量、类方法这些概念全塞进两个实验任务里逼着你从零把Vehicle类写出来再拆成Car和Airplane两个子类跑多态。听起来像教科书作业但真正动手的人会发现这份文档里藏着不少能直接复用到实际项目里的设计思路——比如用静态变量做全局配置、用抽象基类约束接口、用map批量驱动对象。它适合正在学 Python 面向对象的新手照着敲一遍也适合工作几年但没系统整理过 OOP 基础的熟手拿来查漏补缺。下面我就按这份实验的脉络把每一步拆开讲清楚包括代码怎么写、参数怎么传、哪里容易翻车。2. 拆解 Vehicle 类构造函数、析构函数与静态变量的真实用法2.1 为什么先定义一个带静态变量的 Vehicle 类实验第一部分要求定义一个Vehicle类包含品牌brand和型号type两个成员变量并且明确要求“通过静态变量方式设置品牌”。这里有个容易混淆的点Python 里没有像 Java 那样的static关键字所谓静态变量就是定义在类体里、不在__init__里的类属性。它的特点是所有实例共享同一份修改类属性会影响所有对象。实验里让构造函数通过静态变量设置品牌其实是在模拟“全局默认配置”的场景——比如你写一个车辆管理系统所有车辆默认品牌可以先设成某个值后续再单独覆盖。先看核心代码 1 的原始写法class Vehicle(): def __init__(self, brand, type): self._brand brand self._type type def __del__(self): print(self._brand, is deleted) self._brand def set_brand(this, brand): this._brand brand def out(self): return self._brand, self._type def main(): v Vehicle(audi, A6) print(v.out()) v.__del__() v.set_brand(Volkswagen) print(v.out()) v.set_brand(audi) print(v.out()) if __name__ __main__: main()这段代码能跑但有几个细节值得说。__init__里self._brand和self._type用了单下划线前缀这是 Python 的约定告诉调用者“这是内部属性别直接碰”。__del__是析构函数对象被垃圾回收时自动调用这里手动调了一次v.__del__()其实不太推荐——手动调析构容易导致重复释放后面避坑章节会细说。set_brand的第一个参数写成了this虽然能跑但 Python 社区约定俗成用self换成this会让读代码的人愣一下。参数说明brand和type都是字符串传入时注意不要传None否则out()返回的元组里会出现None打印出来不好看。set_brand接收一个字符串参数直接覆盖_brand。2.2 静态变量和类方法怎么改才不影响已有对象实验里还要求“定义一个类方法在其中更新交通工具类的静态变量品牌和型号”。类方法用classmethod装饰第一个参数是cls代表类本身。下面是我一般会写的版本class Vehicle: # 类属性所有实例共享 default_brand unknown default_type unknown def __init__(self, brandNone, typeNone): # 如果实例没传品牌就用类属性兜底 self._brand brand if brand is not None else Vehicle.default_brand self._type type if type is not None else Vehicle.default_type classmethod def update_default(cls, brand, type): # 通过类方法修改类属性影响后续创建的对象 cls.default_brand brand cls.default_type type def out(self): return self._brand, self._type # 使用 v1 Vehicle(audi, A6) print(v1.out()) # (audi, A6) Vehicle.update_default(大众, 通用) v2 Vehicle() print(v2.out()) # (大众, 通用)逻辑说明default_brand和default_type定义在类体里是类属性。__init__里用brand if brand is not None else Vehicle.default_brand做兜底这样创建对象时可以不传参数。update_default是类方法通过cls.default_brand修改类属性之后新建的对象都会用新默认值。注意已经创建的对象v1的_brand还是audi因为实例属性一旦赋值就独立于类属性了。这一点实验文档里没展开但实际写代码时经常有人以为改了类属性所有对象都会变结果发现老对象纹丝不动这就是踩坑点。参数说明update_default接收两个字符串分别赋给cls.default_brand和cls.default_type。如果只想改品牌可以把型号参数设成None然后加判断但实验要求两个都更新就保持两个参数。提示类属性适合放全局默认值、计数器、配置项但不要放可变对象比如列表否则所有实例共享同一个列表改一个全变。3. 继承与多态抽象基类、子类实现和 run 函数的配合3.1 抽象类 Vehicle 和 abstractmethod 的约束力实验第二部分要求定义一个抽象类Vehicle包含抽象方法drive然后让Car和Airplane继承并实现。Python 里抽象基类靠abc模块实现核心代码 2 的写法是from abc import abstractmethod, ABCMeta class Vehicle(metaclassABCMeta): abstractmethod def drive(self): pass class Car(Vehicle): def drive(self): print(驾驶员:%s\n 汽车型号:%s\n 行驶年数:%d % (LZG, AE86, 3)) class Airplane(Vehicle): def drive(self): print(机长:%s\n 乘务员数量:%d\n 乘客数量:%d % (lzg, 15, 66)) def run(vehicle): print(vehicle.__class__) vehicle.drive() def main(): v [Car(), Airplane()] m map(run, v) list(m) if __name__ __main__: main()这里Vehicle用metaclassABCMeta声明为抽象基类drive用abstractmethod装饰。关键点抽象类不能直接实例化如果你写Vehicle()会报TypeError: Cant instantiate abstract class Vehicle with abstract method drive。子类Car和Airplane必须实现drive否则它们也是抽象类同样不能实例化。这个约束在团队协作里很有用——你定义好接口别人写子类时忘了实现运行时报错比默默跑出错误结果要好。参数说明Car.drive里硬编码了驾驶员LZG、型号AE86、年数3Airplane.drive里硬编码了机长lzg、乘务员15、乘客66。实际项目里这些应该作为实例属性在__init__里传入但实验为了聚焦多态简化了。run函数接收任意Vehicle子类对象先打印类名再调drive这就是多态同一个run函数传入Car输出汽车信息传入Airplane输出飞机信息。3.2 map 驱动对象列表为什么用 list(m) 而不是直接打印main里v [Car(), Airplane()]创建了一个列表里面是两个不同子类的实例。map(run, v)把run函数依次应用到每个元素上返回一个迭代器。Python 3 里map是惰性的不调用list(m)的话run根本不会执行。很多人第一次写会直接print(m)结果输出map object at 0x...以为代码坏了。加上list(m)强制求值才会看到两段输出。如果你想让代码更直观可以用 for 循环替代for vehicle in v: run(vehicle)效果一样但可读性更好。map适合函数式风格for适合命令式风格选哪个看团队习惯。实验文档里用了map你照着写没问题但要知道list(m)这步不能省。注意run函数里print(vehicle.__class__)输出的是class __main__.Car这种形式包含模块名。如果你把类定义在别的模块里输出会带模块路径调试时能快速定位对象来源。4. 避坑与排查这份实验代码里最容易翻车的五个地方4.1 手动调用__del__导致重复释放现象代码里写了v.__del__()运行后打印一次audi is deleted但程序结束时又打印一次因为对象真正被回收时 Python 还会自动调一次析构。原因__del__是析构函数由垃圾回收器触发手动调用只是普通方法调用不会阻止后续自动调用。解决不要手动调__del__。如果需要在对象销毁前做清理用close()方法或上下文管理器with语句把清理逻辑放进去让调用方显式关闭。4.2 类属性被实例属性覆盖后改不回来现象先创建v1 Vehicle(audi, A6)然后Vehicle.update_default(大众, 通用)再v1.out()发现还是(audi, A6)以为类方法没生效。原因v1在__init__里已经给self._brand赋了值实例属性优先于类属性。类方法改的是类属性不影响已有实例的实例属性。解决如果希望所有对象都跟着变就不要在__init__里给实例属性赋值而是让out()直接读self.__class__.default_brand。或者接受这个行为——已有对象保持原值新对象用新默认值这本来就是类属性的语义。4.3 抽象类忘了实现抽象方法实例化时报错现象写了一个新子类Boat(Vehicle)但没写drive方法然后Boat()报TypeError。原因抽象基类里abstractmethod装饰的方法子类必须全部实现否则子类也是抽象类。解决检查子类是否漏了方法或者给抽象方法一个默认实现去掉abstractmethod但那样就失去约束意义了。常见做法是保留抽象方法在子类里补上。4.4map返回迭代器被消费后无法复用现象m map(run, v)之后先list(m)打印了结果再list(m)发现是空列表。原因map返回的迭代器只能遍历一次第一次list(m)已经把它耗尽了。解决如果需要多次遍历把结果存成列表results list(map(run, v))之后对results操作。或者直接用 for 循环每次重新遍历原列表。4.5 字符串格式化里%d传了字符串导致报错现象print(行驶年数:%d % (3))报TypeError: %d format: a number is required, not str。原因%d要求整数传字符串会报错。实验代码里写的是3没加引号所以没事但如果你从输入读进来的是字符串就会翻车。解决用int()转换或者改用 f-stringprint(f行驶年数:{year})f-string 会自动调str()不挑类型。5. 进阶技巧用 dataclass 和类型注解重写这份实验实验代码能跑但放在 2024 年写 Python我一般会用dataclass和类型注解让代码更干净、更不容易出错。下面是我重写的版本功能完全对齐实验要求但可读性和可维护性高不少from abc import ABC, abstractmethod from dataclasses import dataclass, field dataclass class Vehicle(ABC): brand: str unknown type: str unknown abstractmethod def drive(self) - None: ... dataclass class Car(Vehicle): driver: str LZG model: str AE86 years: int 3 def drive(self) - None: print(f驾驶员:{self.driver}\n 汽车型号:{self.model}\n 行驶年数:{self.years}) dataclass class Airplane(Vehicle): captain: str lzg crew: int 15 passengers: int 66 def drive(self) - None: print(f机长:{self.captain}\n 乘务员数量:{self.crew}\n 乘客数量:{self.passengers}) def run(vehicle: Vehicle) - None: print(type(vehicle).__name__) vehicle.drive() if __name__ __main__: vehicles: list[Vehicle] [Car(), Airplane()] for v in vehicles: run(v)逻辑说明dataclass自动生成__init__、__repr__、__eq__省掉手写构造函数的重复代码。Vehicle继承ABC并声明drive为抽象方法子类必须实现。Car和Airplane各自加了业务字段用类型注解标明类型。run函数参数标注VehicleIDE 能给出更好的补全提示。vehicles: list[Vehicle]是 Python 3.9 的泛型写法老版本用List[Vehicle]需要从typing导入。参数说明brand和type有默认值unknown创建对象时可以不传。Car的driver、model、years都有默认值Airplane的captain、crew、passengers同理。run接收Vehicle类型传Car或Airplane都行多态照常工作。对比原实验代码这个版本少了手动__del__、少了map的迭代器陷阱、类型清晰、字段可配置。如果你在写实验报告可以把两个版本都放上去对比说明为什么现代 Python 更推荐 dataclass。从那以后我每次写面向对象代码都会先问自己这个类能不能用 dataclass 简化抽象方法是不是真的需要类属性会不会被实例属性覆盖把这三个问题过一遍基本能避开大部分 OOP 翻车场景。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑