简介这份Python实验报告面向正在学习面向对象程序设计的高校学生与自学者聚焦类与对象、封装、继承、多态等核心概念帮助读者通过动手实验真正理解抽象类、构造函数与析构函数、静态变量与类方法等知识点。资源包内含1个doc文档约323KB以实验报告形式完整呈现实验目的、实验内容、核心代码与运行结果结构清晰便于对照学习。文档围绕交通工具Vehicle类展开先演示成员变量、构造函数、析构函数、成员函数与类方法的定义与调用再通过抽象类Vehicle及Car、Airplane子类展示继承与多态的实现并借助run函数与map遍历体现不同对象对同一消息的差异化响应。目前已有3138人学习适合需要完成课程实验、巩固面向对象编程基础或准备相关练习的读者参考借鉴。1. 从一份实验报告说起面向对象到底在练什么很多人第一次拿到「Python实验一 面向对象程序设计实验.doc」这类任务时第一反应是打开文档、照着题目把类写出来、跑通、截图、交差。但真正做过几轮之后你会发现这个实验真正想让你练的不是「会写 class」而是能不能用对象把现实问题拆干净。我带过几个刚入门的同学他们能默写__init__和self却在一个「图书借阅」或「学生选课」的小场景里把数据、行为、状态全揉进一个类最后改一个需求就牵一发动全身。面向对象程序设计实验的价值恰恰在于逼你把「谁拥有数据、谁负责行为、谁和谁交互」这三件事想清楚。这篇笔记就按一线做法把这份实验从概念、设计、编码到排错完整走一遍适合刚学完语法、准备动手写第一个像样类结构的人也适合想回头把基础打牢的熟手。2. 先想清楚对象再写代码类、属性、方法的拆分逻辑2.1 为什么不能上来就写 class新手最容易翻车的地方是拿到题目直接开编辑器边想边写最后得到一个几百行、什么都干的「上帝类」。面向对象的第一原则是职责单一一个类只描述一类事物只做这一类事物该做的事。实验里常见的场景无非是学生、课程、图书、订单、账户这几类每一类对应一个类。判断标准很简单——如果一句话里出现「学生选课」那「选」这个动作要么属于学生要么属于课程取决于谁持有选课列表。我一般会先在纸上画三列类名、它持有的数据、它能做的事。三列填完类结构基本就定了再动手写代码返工率能降一大半。2.2 属性该放哪、方法该给谁属性归属的判断依据是「谁的状态」。学生的姓名、学号属于学生课程的容量、已选人数属于课程。方法归属则看「谁发起这个动作」。选课这个动作如果由学生发起就写成Student.select(course)内部再去调用course.add_student(self)。这样两个类各自维护自己的数据谁都不越界。实验里经常要求统计、查询、修改这些操作不要塞进实体类可以单独抽一个管理类比如CourseManager专门负责增删查。实体类管状态管理类管流程这是最稳的分工。2.3 用最小代码验证类结构设计完先别写完整逻辑用一段最小代码把类骨架和交互跑通确认职责没串。下面这段是常见的学生选课骨架class Student: def __init__(self, sid, name): self.sid sid # 学号唯一标识 self.name name # 姓名 self.courses [] # 已选课程列表属于学生自己的状态 def select(self, course): # 学生发起选课把自身交给课程去登记 if course.add_student(self): self.courses.append(course) return True return False class Course: def __init__(self, cid, name, capacity): self.cid cid # 课程号 self.name name # 课程名 self.capacity capacity # 容量上限 self.students [] # 已选学生列表属于课程自己的状态 def add_student(self, student): # 课程负责判断容量容量满则拒绝 if len(self.students) self.capacity: return False self.students.append(student) return True这段代码的关键在于容量判断放在Course里因为容量是课程的状态选课记录同时写进两边保证双向可查。参数capacity是硬约束改大改小直接影响行为实验里如果要求「满员提示」就在这里返回False让上层处理。跑通骨架后再补查询、退课、统计思路不会乱。2.4 继承和多态在实验里怎么用才不别扭实验通常要求体现继承。常见做法是抽一个基类比如Person让Student和Teacher继承它共享name、uid这些字段。但要注意继承是为了复用和统一接口不是为了炫技。如果两个类只是名字像、行为完全不同硬套继承反而添乱。多态则体现在「同一个方法名不同类有不同实现」比如基类定义describe()子类各自返回自己的描述。实验里能自然用上就用用不上别硬凑评审看的是设计是否合理不是继承层数。3. 把实验跑起来从建类到验证的完整操作路径3.1 环境与文件组织这份实验用纯 Python 就能完成不需要额外依赖。建议按下面的结构组织文件避免所有代码堆在一个.py里oop_lab/ ├── models.py # 实体类Student、Course 等 ├── manager.py # 管理类增删查逻辑 └── main.py # 入口构造数据并验证models.py只放实体manager.py只放流程main.py负责组装和测试。这样拆分的好处是改实体不影响流程改流程不动实体实验报告里也容易画类图。运行方式就是python main.py不需要虚拟环境也能跑但养成建虚拟环境的习惯没坏处。3.2 构造测试数据并验证核心行为骨架有了接下来用真实数据把每条路径都走一遍。下面这段构造了两个学生、一门容量为 1 的课程专门验证满员逻辑from models import Student, Course if __name__ __main__: s1 Student(S001, A同学) s2 Student(S002, B同学) c1 Course(C001, Python程序设计, capacity1) print(s1.select(c1)) # True第一个选上 print(s2.select(c1)) # False容量已满被拒 print(len(c1.students)) # 1课程侧记录正确 print(len(s2.courses)) # 0被拒的学生不应有记录这段验证的重点是最后两行被拒绝的选课不能留下脏数据。很多同学的 bug 就出在这里——课程侧拒绝了学生侧却已经把课程加进列表导致两边数据不一致。参数capacity1是故意设小方便触发边界。实验里凡是涉及数量、余额、库存的地方都要用这种「刚好越界」的数据去测。3.3 退课与数据一致性选课写完了退课是另一半。退课要同时清理两边的记录任何一边漏了都会导致后续统计出错。常见写法是在Student上加drop内部调用course.remove_student(self)class Student: # 省略 __init__ 和 select def drop(self, course): if course in self.courses: self.courses.remove(course) course.remove_student(self) return True return False class Course: # 省略 __init__ 和 add_student def remove_student(self, student): if student in self.students: self.students.remove(student)这里用in先判断再删避免对不存在的记录操作抛异常。参数没有额外可调的但顺序有讲究先删自己再通知对方还是反过来取决于异常处理策略。我一般先删自己再通知对方因为自己是动作发起方失败概率更低。退课之后一定要重新打印两边的列表确认一致这是最省事的自检手段。3.4 用断言把验证固化下来手动 print 看结果容易漏建议把关键验证写成断言跑一次全过就放心assert s1.select(c1) is True assert s2.select(c1) is False assert len(c1.students) 1 assert len(s2.courses) 0 s1.drop(c1) assert len(c1.students) 0 assert len(s1.courses) 0断言的好处是把「预期行为」写进代码改逻辑时一跑就知道有没有破坏原有约定。实验报告里附上这段比截图更有说服力。参数方面没有可调的但断言要覆盖正常路径和边界路径两类缺一不可。4. 避坑与排查面向对象实验里最容易翻车的五件事4.1 可变默认参数导致数据串台现象两个不同学生对象的课程列表莫名其妙共享同一份数据选了一个另一个也跟着变。原因在__init__里写了def __init__(self, courses[])Python 的默认参数只在定义时求值一次所有实例共用同一个列表。解决默认值一律用None在函数体内再初始化写成self.courses [] if courses is None else courses。这是最经典的坑没有之一。4.2 self 漏写导致属性变成类变量现象改了一个实例的属性另一个实例也跟着变。原因赋值时漏了self.比如写成name name而不是self.name name结果创建的是局部变量或类变量。解决所有实例属性赋值前都带self.写完用print(s1.name, s2.name)对比确认。这个坑隐蔽性强因为代码不报错只是行为不对。4.3 双向关系只维护了一边现象从学生侧能查到课程从课程侧查不到学生统计人数对不上。原因选课或退课时只更新了一方的列表。解决把「同时更新两边」封装成一个方法任何入口都走它不要在两处分别写。上面select和drop的写法就是为此。排查时打印两边的长度对比不一致就是这个问题。4.4 继承里忘了调用父类初始化现象子类实例缺少父类定义的属性访问时报AttributeError。原因子类写了__init__却没调super().__init__(...)。解决子类初始化第一行就调父类把公共字段传上去。如果实验里用了继承这条几乎必踩写完先实例化一个子类把父类属性挨个访问一遍。4.5 把业务判断塞进实体类导致难测试现象实体类里出现大量 if-else 和外部依赖单独测试一个类要构造一堆环境。原因职责没分清流程逻辑混进了实体。解决实体类只维护自身状态和最基本的行为复杂判断抽到管理类。排查方法是问自己「这个类能不能脱离其他类单独实例化并测试」不能就说明耦合过重。5. 进阶技巧让这份实验从及格变成能拿得出手把基本功能跑通只是及格线真正让这份面向对象实验有价值的是后面这几步。第一步是用__repr__给每个实体类加一个清晰的字符串表示调试和打印时一眼能看懂对象内容比默认的object at 0x...强太多。写法很简单返回一个包含关键字段的字符串即可比如fStudent({self.sid}, {self.name})。第二步是把管理类的查询接口设计成返回新列表而不是内部列表本身避免外部代码直接改到内部数据这是封装意识的体现。第三步是给关键方法补上类型注解参数和返回值都标清楚实验报告里显得专业自己回头看也不容易忘。再往上一层可以引入简单的异常处理替代返回False。比如容量满时抛一个自定义的CourseFullError调用方用 try 捕获。这样做的好处是错误不会被忽略坏处是调用方必须处理。实验里如果要求「友好提示」用异常更合适如果只是内部判断返回布尔值更轻。两种都行关键是一致不要一半返回布尔一半抛异常。验证方面除了断言可以写一个极简的测试函数把所有场景串起来跑一遍输出通过项数。不需要引入测试框架纯 Python 就够。最后我想说的是我早期做这类实验也翻过车把选课逻辑写在一个类里后来加个「退课」需求改了整整一下午。从那以后我养成了一个习惯动手前先花十分钟把类和职责写在纸上宁可前期慢也不后期返工。这个习惯比任何语法技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取