news 2026/8/30 8:09:12

Python面向对象编程:类与继承核心知识详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python面向对象编程:类与继承核心知识详解

初学 Python 时,很多人都有过这样的经历:函数写了一堆,逻辑能跑通,可一旦业务复杂起来,代码就开始“失控”——要么重复代码太多,要么改一个需求要牵连好几个函数。

这些都是从面向过程编程转向面向对象编程时必经的坎。本文将围绕MIT 6.0001 计算机科学导论第9讲的内容,系统整理 Python 中(Class)和继承(Inheritance)的核心知识点,用大量可运行的代码示例,带你把 OOP 的地基打扎实。

无论你是刚看完 Python 基础语法的新手,还是想系统回顾面向对象特性的开发者,这篇文章都值得认真读完。你不仅能学会如何定义类、创建对象、理解继承和多态,还能掌握实际项目中关于类设计的通用建议。

1. 为什么要学习类和继承

1.1 从面向过程到面向对象

Python 首先是一门面向过程的语言,写脚本时我们习惯从上到下、按函数拆分子任务。但现实世界的软件业务往往涉及大量实体和状态,比如:

  • 学生有姓名、学号、成绩;
  • 银行账户有余额、卡号、存取操作;
  • 订单有商品、金额、用户、支付状态。

如果都用单独的变量和函数管理,数据分散且不易维护。面向对象编程(OOP)的核心思想是:把数据和对数据的操作封装在一起,形成一个“对象”。

类(Class)就是对象的模板,对象(Object)是模板的具体实例。用类组织代码后,你会明显感受到三个好处:

  1. 数据更聚合,状态和行为放在同一处。
  2. 代码复用性更高,多个实例共享类结构。
  3. 逻辑更贴近真实世界,建模和后期维护都更直观。

1.2 类和对象的直观理解

用一个简单的比喻:类是建筑的施工图,对象是按照施工图盖出来的楼。

施工图里定义了每层有几个房间、每间房多大面积,但施工图本身不是房子。我们同一个施工图可以盖出很多栋结构相同的楼,每栋楼住的人不同,房间摆设也不同。

在 Python 中,类的定义并不占用真正的业务数据,只有实例化后,对象才会真正“住进数据”。这种设计带来的好处是:我们只用编写一次类,就可以创建多个互不干扰的对象。

1.3 为什么 MIT 6.0001 第9讲专门讲类和继承

MIT 6.0001 是麻省理工学院计算机科学导论课程,使用的语言正是 Python。在第9讲之前,课程已经完成了基本语法、函数、递归、调试等基础教学;第9讲则是从“过程化编程”跨入“面向对象编程”的关键节点。

这一讲的核心目标有三个:

  • 学会定义类、创建对象、理解实例变量和方法。
  • 理解继承如何实现“子类复用父类代码”。
  • 通过案例体会 OOP 在模拟真实系统时的优势。

后面第10讲、第11讲会继续深入面向对象设计和程序调试,所以第9讲是承上启下的一课。

2. 环境准备与版本说明

类和继承属于 Python 语言核心语法,几乎不需要第三方库。只要你的电脑上能运行 Python 3,就可以直接运行本文所有示例。

2.1 语言版本选择

本文示例基于 Python 3。Python 2 官方早已停止维护,不建议新项目继续使用。

你可以通过命令行查看版本:

python --version

如果输出是Python 3.x.x,说明环境正常。如果你还没有安装 Python,可以到 Python 官网下载对应操作系统的安装包。

2.2 运行方式说明

  • 如果你用命令行,将代码保存为.py文件,然后执行:
python demo.py
  • 如果你使用 Jupyter Notebook 或 VS Code,直接逐行运行即可。
  • 在学习类的时候,强烈建议打开一个 Python 交互式环境(python进入 REPL),边敲边观察结果。

2.3 本文代码项目结构

为了阅读方便,下文示例会按“概念示例”和“完整实战”两个层次组织。完整实战部分会使用以下结构:

course_system/ ├── models.py # 定义相关类 ├── main.py # 运行入口 └── README.md # 项目说明(可选)

你可以先建立一个自己的练习目录,把每个示例保存为独立文件,便于反复修改测试。

3. Python 类的基础语法

在深入继承之前,我们要先掌握类的基本定义方式。

3.1 定义一个最简单的类

Python 中使用class关键字定义类,类名通常使用大驼峰命名法(CamelCase)。

# 文件路径:course_system/models.py class Person: pass

这段代码定义了一个空类Personpass表示暂时什么都不做,在 Python 中用于占位。

创建对象的方式是:类名后加括号。

p1 = Person() p2 = Person() print(p1) # 输出类似 <__main__.Person object at 0x...> print(p2)

此时p1p2是两个完全独立的对象。即使它们来自同一个类,修改其中一个的属性不会影响另一个。

3.2__init__方法与self

空类意义不大。我们希望每个对象在创建时就能拥有自己的数据,比如Person应该有一开始就知道的姓名和年龄。

Python 通过__init__方法完成初始化。它会在对象创建后自动被调用,第一个参数固定是self,代表当前被创建的对象本身。

class Person: def __init__(self, name, age): self.name = name self.age = age

创建对象:

p1 = Person("Alice", 20) p2 = Person("Bob", 21) print(p1.name) # Alice print(p2.age) # 21

这里的self.name = name表示:在当前对象上添加一个名为name的实例变量,并把传入参数的值赋给它。

需要注意,__init__不是构造函数,而是初始化方法。真正分配对象内存的步骤由 Python 内部完成,__init__只是负责给新对象设置初始状态。

3.3 实例方法与属性

对象可以保存数据,也可以拥有行为。在类中定义的函数叫方法,方法的第一个参数通常也是self,这样方法内部才能访问当前对象的数据。

class Person: def __init__(self, name, age): self.name = name self.age = age def introduce(self): return f"我叫{self.name},今年{self.age}岁。" def birthday(self): self.age += 1

调用方式:

p1 = Person("Alice", 20) print(p1.introduce()) # 我叫Alice,今年20岁。 p1.birthday() print(p1.age) # 21

实例方法通过对象.方法名()调用,Python 会自动把对象本身作为第一个参数传入。所以p1.introduce()等价于Person.introduce(p1)

这一点新手经常忽略:定义方法时如果不写self,调用时往往会出现TypeError: introduce() takes 0 positional arguments but 1 was given之类的报错。

3.4 属性访问控制与命名习惯

Python 没有 Java 中privatepublic这样的强制访问控制,而是依赖命名约定:

  • name:普通公开属性,外部可以直接访问。
  • _name:约定为“受保护”属性,提醒外部不要随意访问,但 Python 不会强制拦截。
  • __name:双下划线开头会触发名称改写,在类内部仍然可以访问,外部直接访问会报错,主要用于避免子类中意外覆盖。
class Person: def __init__(self, name, age): self._name = name # 受保护命名 self.__age = age # 名称改写 def get_age(self): return self.__age

外部访问__age时会发现属性不存在,但这并不是严格意义上的私有变量,只是 Python 对名称做了改写(变成了_Person__age)。因此不要依赖双下划线做安全控制,它更像一个“请勿随意访问”的标识。

3.5 特殊方法__str____repr__

直接打印对象时,默认输出是对象的内存地址字符串,不够友好。我们可以通过__str__方法控制打印效果。

class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"Person(name={self.name}, age={self.age})"
p1 = Person("Alice", 20) print(p1) # Person(name=Alice, age=20)

__str__面向用户,__repr__面向开发者。在调试时,repr()通常用于展示更完整的对象信息。实际项目中如果希望日志和打印都友好,建议两个方法都实现一下。

4. 继承的核心机制

4.1 继承解决什么问题

开发学生管理系统时,你可能会发现StudentTeacher都需要姓名、年龄、自我介绍等能力。如果为每个类重复编写同样的代码,既浪费时间,又难以统一维护。

继承允许我们定义一个通用的父类(基类),然后在子类中复用父类的属性与方法,同时扩展自己的特性。

Person(父类,通用属性:name, age) ├── Student(子类,增加 student_id) └── Teacher(子类,增加 course)

4.2 子类的定义与super()

继承的语法是在类名后加括号,括号中写父类。

class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id

这里super().__init__(name, age)的作用是:调用父类Person__init__方法,先完成nameage的初始化,再为子类补充student_id

如果不调用super(),父类的__init__不会自动执行,子类对象就没有父类中定义的属性。

s1 = Student("小明", 20, "20230001") print(s1.name) # 小明 print(s1.student_id) # 20230001

4.3 方法重写

子类可以定义与父类同名的方法,这种行为叫覆盖。调用子类对象的方法时,会优先找到子类自己的版本。

class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id def introduce(self): return f"我叫{self.name},是一名学生,学号是{self.student_id}。"
s1 = Student("小明", 20, "20230001") print(s1.introduce()) # 我叫小明,是一名学生,学号是20230001。

如果仍然想调用父类的方法,可以在子类方法中主动调用super().方法名()

class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id def introduce(self): parent_info = super().introduce() return f"{parent_info} 我的学号是{self.student_id}。"

4.4isinstanceissubclass

Python 提供了两个内置函数帮助检查对象与类的关系:

class Person: pass class Student(Person): pass s1 = Student() print(isinstance(s1, Student)) # True,s1 是 Student 实例 print(isinstance(s1, Person)) # True,Student 继承自 Person print(issubclass(Student, Person)) # True,Student 是 Person 的子类

这里isinstance(s1, Person)返回True是理解多态的关键:子类对象可以看作父类类型的一种。

4.5 多重继承

Python 支持一个子类继承多个父类,但实际使用时要非常谨慎。

class A: def method(self): print("A method") class B: def method(self): print("B method") class C(A, B): pass c = C() c.method() # A method

当父类中存在同名方法时,Python 会按照方法解析顺序(MRO)查找。可以通过类名.__mro__查看查找顺序:

print(C.__mro__) # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

MRO 的完整规则较复杂,日常开发中如果出现复杂的多重继承结构,往往意味着类设计需要重新审视。

5. 完整实战案例:校园人员管理系统

下面我们用类和继承写一个完整的“校园人员管理系统”,把这一讲的核心知识点串起来。

5.1 需求分析

系统需要管理三类人:通用人员、学生、教师。

  • 通用人员:有姓名、年龄、基本介绍能力。
  • 学生:在通用人员基础上增加学号,并能计算班级平均分(简化场景)。
  • 教师:在通用人员基础上增加课程字段,并能打印课程信息。

功能要求:

  • 创建一个学生对象和一个教师对象。
  • 分别调用各自的介绍方法。
  • 展示继承、重写和super()的实际用法。

5.2 定义基础类:Person

# 文件路径:course_system/models.py class Person: """通用人员类,所有人员类型的基类""" def __init__(self, name, age): self.name = name self.age = age def introduce(self): return f"我叫{self.name},今年{self.age}岁。" def __str__(self): return self.introduce()

说明:introduce是通用行为,子类可以根据需要重写。

5.3 定义学生类:Student

# 文件路径:course_system/models.py class Student(Person): """学生类,继承 Person""" def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id def introduce(self): # 先复用父类介绍逻辑,再补充学生特有内容 parent_info = super().introduce() return f"{parent_info} 我是一名学生,学号是{self.student_id}。" def add_score(self, score): """给学生添加一门课程成绩,简单维护在一个列表中""" if not hasattr(self, "scores"): self.scores = [] self.scores.append(score) def average_score(self): """计算平均分,没有成绩时返回 0""" if not hasattr(self, "scores") or len(self.scores) == 0: return 0 return sum(self.scores) / len(self.scores)

这里用hasattr判断对象上是否已经存在scores属性,避免在__init__中初始化列表时出现“属性共享”的坑。更规范的做法是直接在__init__中设置self.scores = [],下面的代码也会这么做。

为了代码更清晰,我们把__init__改一下:

class Student(Person): def __init__(self, name, age, student_id): super().__init__(name, age) self.student_id = student_id self.scores = [] def add_score(self, score): self.scores.append(score) def average_score(self): if not self.scores: return 0 return sum(self.scores) / len(self.scores)

这样做更符合常规习惯,也不容易出现可变默认参数问题。

5.4 定义教师类:Teacher

# 文件路径:course_system/models.py class Teacher(Person): """教师类,继承 Person""" def __init__(self, name, age, course): super().__init__(name, age) self.course = course def introduce(self): parent_info = super().introduce() return f"{parent_info} 我是一名教师,负责讲授{self.course}课程。" def give_lecture(self): return f"今天开始讲:{self.course}课程的第一章。"

5.5 编写运行入口

# 文件路径:course_system/main.py from models import Student, Teacher def main(): alice = Student("Alice", 20, "20230001") bob = Student("Bob", 21, "20230002") prof = Teacher("Wang", 35, "计算机科学导论") alice.add_score(90) alice.add_score(85) bob.add_score(78) print(alice.introduce()) print(bob.introduce()) print(prof.introduce()) print(f"\nAlice 的平均成绩:{alice.average_score()}") print(f"Bob 的平均成绩:{bob.average_score()}") print(f"\n{prof.name} 即将开始上课:{prof.give_lecture()}") if __name__ == "__main__": main()

5.6 运行与预期输出

在终端执行:

cd course_system python main.py

预期输出:

我叫Alice,今年20岁。 我是一名学生,学号是20230001。 我叫Bob,今年21岁。 我是一名学生,学号是20230002。 我叫Wang,今年35岁。 我是一名教师,负责讲授计算机科学导论课程。 Alice 的平均成绩:87.5 Bob 的平均成绩:78 Wang 即将开始上课:计算机科学导论课程的第一章。

整个案例展示了:

  • 父类Person统一管理通用属性nameage
  • 子类通过super().__init__()复用父类初始化逻辑。
  • 子类通过重写introduce()展示不同身份的介绍内容。
  • 对象本身承载数据,实例之间互不影响。

6. 常见问题与排查思路

问题现象常见原因解决思路
调用子类方法时报TypeError: __init__() missing 1 required positional argument子类__init__里忘了调用super().__init__(),导致父类属性未初始化在子类__init__第一行使用super().__init__(...)初始化父类属性
方法定义时没写self,调用时报参数数量错误Python 方法调用时会自动传入对象本身作为第一个参数定义实例方法时第一个参数写self
两个对象之间共享了列表或字典属性在类中直接定义可变变量,如scores = [],而不是通过self定义实例变量__init__中使用self.scores = [],避免所有实例共用同一个列表
打印对象时显示地址字符串<__main__.Person object at ...>没有实现__str____repr__在类中定义__str__方法,返回可读字符串
调用父类方法时出现AttributeError: 'Student' object has no attribute 'name'子类__init__里没有调用父类初始化确保子类__init__中先执行super().__init__()
多重继承下方法调用不符合预期父类之间存在同名方法,MRO 顺序与直觉不同使用ClassName.__mro__查看查找顺序;如果结构太复杂,优先考虑组合而不是多重继承

6.1 经典坑:可变默认参数

这是初学者经常忽略的问题。在看下面这段代码前,先思考输出是什么:

class Student: def __init__(self, name, scores=[]): self.name = name self.scores = scores s1 = Student("Alice") s1.scores.append(90) s2 = Student("Bob") print(s2.scores) # ?

完整解释:Python 在函数定义时只会创建一次默认列表,所有没传scores参数的对象都会引用同一个列表。因此s2.scores会把Alice的成绩也打印出来。

正确写法:

class Student: def __init__(self, name, scores=None): self.name = name self.scores = scores if scores is not None else []

实际项目中,凡是用可变对象(列表、字典、集合)做默认值,都应该警惕。

6.2 排查清单

如果你在练习中遇到了问题,可以按以下顺序排查:

  1. 检查类名是否拼写正确,Python 是大小写敏感的语言。
  2. 检查__init__是否写成了_init_,少写下划线是一个非常典型的笔误。
  3. 检查子类是否调用了super().__init__()
  4. 检查方法内部是否使用self.属性名访问实例变量。
  5. 打印对象.__dict__,该属性会展示对象所有实例变量,非常方便排查状态问题。

7. 最佳实践与工程建议

类与继承虽然好用,但如果不加约束,很容易写出难以维护的代码。以下建议来自日常项目和课程实践,供你参考。

7.1 优先组合,谨慎继承

继承能复用代码,但也会引入紧耦合。父类一旦变化,所有子类都可能受影响。

如果一个类只是“需要某个能力”,而不是“某个能力的子类型”,那么组合更合适:

class Course: def __init__(self, name, teacher): self.name = name self.teacher = teacher

此时Course中持有Teacher对象,而不是继承Teacher。这个原则在团队协作中尤其重要,它可以显著降低代码间的耦合度。

7.2 合理地使用方法重写

重写方法时,尽量通过super().方法名()复用父类逻辑,避免完全丢弃原有行为。这样做的好处是:

  • 父类的功能仍然生效。
  • 后续父类修复 bug,子类自动受益。
  • 代码意图更清晰:读者能看到“先做父类的事,再做子类特有的事”。

7.3 不要在类级别定义可变对象

以下写法是错误示范:

class Student: scores = [] # 所有实例共享同一个列表

正确做法:

class Student: def __init__(self, name): self.name = name self.scores = []

如果确实需要类级别的共享数据,请用不可变对象或者明确说明它是类变量,而不是实例变量。

7.4 利用特殊方法提升可调试性

实现__str____repr__是低成本、高收益的习惯。哪怕只是简单返回f"Student(name={self.name})",也会让调试日志清晰很多。

7.5 多态是面向对象的灵魂

多态可以概括为:相同的方法名,在不同类型的对象上表现出不同的行为。

在校园系统示例中,alice.introduce()prof.introduce()调用的是同一个方法名,但输出完全不同。实际项目里,一个函数可以对所有 Person 子类统一处理:

def print_introduce(person): print(person.introduce()) print_introduce(alice) print_introduce(prof)

这种方式让代码更有弹性:未来增加新人员类型,只要它继承了Person并实现或继承了introduce,现有调用代码就不需要修改。

7.6 命名规范建议

  • 类名使用大驼峰,例如StudentManager
  • 方法名和变量名使用小写下划线,例如average_score
  • 私有不希望外部直接访问的属性可以加一个下划线前缀,例如_internal_data
  • 避免使用listdictid等内置名称作为变量名。

8. 总结与下一步学习

本文从 MIT 6.0001 第9讲的主题出发,系统梳理了 Python 类和继承的知识框架:

  • 类使用class定义,对象通过类名()创建。
  • __init__负责初始化实例状态,self表示当前对象。
  • 实例方法默认接收self参数,通过self.属性名访问实例数据。
  • 继承通过class 子类(父类)实现,子类中通过super()调用父类方法。
  • 子类可以重写父类方法,以实现不同的行为,这就是多态的基础。
  • isinstanceissubclass可以检查对象或类之间的继承关系。

下一步建议你继续学习:

  1. Python 的abstractmethod抽象类,了解如何强制子类实现指定方法。
  2. @property装饰器,用更优雅的方式管理属性访问。
  3. 类方法与静态方法(@classmethod@staticmethod),理解它们与实例方法的区别。
  4. 在真实项目中练习:写一个小型命令行学生管理系统,把类、继承、文件读写结合起来。

类与继承不是面试八股,而是日常开发中组织代码的核心手段。动手敲代码是最快的理解方式。建议你按照第5章的校园人员管理系统,自己添加一个Staff(行政人员)类,增加工号属性和打卡方法,再为Student添加一个print_report()方法。完成这些练习后,你对 Python 面向对象的理解会更扎实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 8:07:13

OpenAI自研Jalapeño芯片:效率与速度双提升,AI算力基建变局

最近如果你跑过规模稍大的模型训练&#xff0c;或者在生产环境里调过推理服务&#xff0c;大概率会有同样感受&#xff1a;GPU 永远是稀缺资源&#xff0c;训练队列要排队&#xff0c;推理账单掉起来比钱包还快。正因为这样&#xff0c;看到“OpenAI Jalapeo 芯片实现效率与速度…

作者头像 李华
网站建设 2026/8/30 8:05:53

邻域注意力Transformer在LAD三维分割中的应用

临床评估左前降支&#xff08;Left Anterior Descending Artery&#xff0c;LAD&#xff09;的狭窄程度前&#xff0c;第一步往往是在冠脉 CTA 三维体数据里把 LAD 完整地分割出来。LAD 是冠状动脉最重要的一支&#xff0c;供血范围覆盖左心室前壁、室间隔前部大部分区域和心尖…

作者头像 李华
网站建设 2026/8/30 8:05:38

终端AI编程可视化预览:/show-me斜杠命令实测

/show-me 这个斜杠命令工具&#xff0c;过去两周在终端 AI 编程工具里安装量破了 5000。5000 对大众软件不算什么&#xff0c;但放到 CLI 插件里&#xff0c;已经说明它踩中了一个很真实的痛点&#xff1a;让跑在终端里的 AI 编程助手&#xff0c;不再只会输出文字&#xff0c;…

作者头像 李华
网站建设 2026/8/30 8:05:35

人形机器人开发实战:从PyBullet仿真到工程落地

人形机器人被视为机器人领域最接近“通用平台”的品类&#xff0c;但许多人第一次动手时&#xff0c;往往会被它庞大的知识面吓到。随着宇树科技以“A股人形机器人第一股”的身份推进IPO&#xff0c;市场开始重新评估机器人赛道的产业价值。对工程师来说&#xff0c;这个信号的…

作者头像 李华
网站建设 2026/8/30 8:05:04

DBeaver 数据透视表字段选择完全指南:3 步自定义显示字段

DBeaver 数据透视表字段选择完全指南&#xff1a;3 步自定义显示字段 【免费下载链接】dbeaver Free universal database tool and SQL client 项目地址: https://gitcode.com/GitHub_Trending/db/dbeaver 在 DBeaver 里跑完查询&#xff0c;往往回给你几百行结果&#…

作者头像 李华
网站建设 2026/8/30 8:03:14

1B 参数跑赢 72B VLM:MinerU PDF 转 Markdown 低显存完整指南

1B 参数跑赢 72B VLM&#xff1a;MinerU PDF 转 Markdown 低显存完整指南 【免费下载链接】MinerU Transforms complex documents like PDFs and Office docs into LLM-ready markdown/JSON for your Agentic workflows. 项目地址: https://gitcode.com/GitHub_Trending/mi/M…

作者头像 李华