文章目录
- 一、为什么需要类?先看 “字典 + 函数” 的痛点
- 场景:管理 3 个学生的信息(姓名、年龄、多门成绩)
- 用 “字典 + 函数” 处理(繁琐)
- 用 “类” 处理(简洁)
- 二、类的基础:从定义到实例化
- 1. 定义类:用`class`关键字
- 示例:定义`Person`类(基础类)
- 2. 创建实例:根据类生成具体对象
- 示例:创建`Person`实例
- 3. 访问属性和方法:实例的核心操作
- 示例:操作`Person`实例
- 4. 类属性:所有实例共享的属性
- 示例:定义和使用类属性
- 三、类的进阶:继承与方法重写
- 1. 继承的基本用法:子类继承父类
- 示例:`Student`类继承`Person`类
- 2. 多继承(可选):子类继承多个父类
- 3. 组合:类之间的 “包含” 关系
- 示例:`Student`类包含`Course`类的实例
- 四、综合实操:学生管理系统(类版)
- 完整代码
- 运行结果
- 五、新手必踩的 5 个坑:避坑指南
- 坑 1:忘记写`__init__`方法,导致实例没有属性
- 坑 2:`self`参数遗漏或位置错误
- 坑 3:继承时忘记调用`super().__init__`
- 坑 4:混淆实例属性和类属性
- 坑 5:方法调用时传了`self`参数
- 六、小结与下一篇预告
- 这篇你学到了什么?
- 下一篇预告
欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们掌握了函数和模块化,能把重复逻辑封装成函数,通过模块复用代码。但在处理 “相似对象” 时(比如多个学生、多个教师),函数 + 字典的组合会显得繁琐:比如每个学生要存姓名、年龄、成绩,每个教师要存姓名、年龄、工资,用字典存储时,每次创建对象都要重复定义键,调用函数时还要频繁传递字典参数,既不直观也容易出错。
今天咱们要学 Python 的 “面向对象核心”——类(Class)。它就像一个 “对象模板”,能把 “数据(属性)” 和 “操作数据的逻辑(方法)” 封装在一起。比如用Student类定义学生的模板,包含 “姓名、年龄、成绩” 这些属性,以及 “显示成绩、计算平均分” 这些方法,创建具体学生(比如小明、小红)时,直接用模板实例化即可,不用重复写字典和函数。学会类,你就能更灵活地模拟现实中的对象,为后面的复杂项目(比如游戏角色、Web 用户系统)打基础。
一、为什么需要类?先看 “字典 + 函数” 的痛点
在学类的语法前,先通过 “处理学生信息” 这个场景,对比 “字典 + 函数” 和 “类” 的差异,感受类的价值。
场景:管理 3 个学生的信息(姓名、年龄、多门成绩)
用 “字典 + 函数” 处理(繁琐)
python
运行
# 1. 用字典存储每个学生的信息student1={"name":"小明","age":15,"scores":[85,92,76]}student2={"name":"小红","age":14,"scores":[95,88,90]}student3={"name":"小刚","age":15,"scores":[78,80,65]}# 2. 用函数处理学生数据(计算平均分、显示信息)defcalculate_average(scores):"""计算平均分"""returnsum(scores)/len(scores)ifscoreselse0defshow_student_info(student):"""显示学生信息"""avg=calculate_average(student["scores"])print(f"姓名:{student['name']},年龄:{student['age']},平均分:{avg:.1f}")# 3. 逐个处理学生(每次都要传字典参数)show_student_info(student1)show_student_info(student2)show_student_info(student3)这段代码能运行,但有两个明显痛点:
- 重复定义结构:每个学生字典都要写
"name"“age”“scores” 这些键,容易漏写或写错; - 函数依赖外部参数:
show_student_info必须传student字典,调用时要确保字典结构正确,否则会报KeyError(比如漏了"scores"键)。
用 “类” 处理(简洁)
用Student类定义学生模板,创建实例时直接传参数,调用方法时不用传字典:
python
运行
# 1. 定义Student类(模板)classStudent:"""学生类,封装学生的属性和方法"""def__init__(self,name,age,scores):# 初始化属性(姓名、年龄、成绩)self.name=name self.age=age self.scores=scores# 方法1:计算平均分defcalculate_average(self):returnsum(self.scores)/len(self.scores)ifself.scoreselse0# 方法2:显示学生信息defshow_info(self):avg=self.calculate_average()print(f"姓名:{self.name},年龄:{self.age},平均分:{avg:.1f}")# 2. 创建学生实例(具体对象)student1=Student("小明",15,[85,92,76])student2=Student("小红",14,[95,88,90])student3=Student("小刚",15,[78,80,65])# 3. 调用实例方法(直接用实例调用,不用传参数)student1.show_info()student2.show_info()student3.show_info()运行结果和前面完全一致,但代码更简洁:
- 不用重复定义字典键,类的
__init__方法已经固定了属性结构; - 调用方法时不用传字典,实例
student1直接调用show_info()即可,因为方法内部能直接访问实例的属性(self.name等)。
这就是类的核心价值:封装对象的 “属性” 和 “方法”,统一结构,减少重复代码,降低调用成本。
二、类的基础:从定义到实例化
类是 “对象的模板”,实例是 “根据模板创建的具体对象”。比如Student类是模板,student1(小明)是实例。咱们从 “定义类”“创建实例”“访问属性和方法” 这三个核心步骤入手,逐步掌握类的基础用法。
1. 定义类:用class关键字
定义类的基本语法如下,核心是class关键字、类名、__init__方法(初始化属性)和自定义方法(操作逻辑):
python
运行
class类名:"""类的文档字符串(描述类的功能)"""def__init__(self,参数1,参数2,...):"""初始化实例的属性(构造方法)"""# 给实例绑定属性,self.属性名 = 参数self.属性1=参数1self.属性2=参数2def方法名1(self,可选参数...):"""自定义方法(操作属性的逻辑)"""# 方法体,可访问self.属性passdef方法名2(self,可选参数...):"""另一个方法"""pass几个关键概念:
class:告诉 Python “这是一个类”,类名要遵循 “驼峰命名法”(每个单词首字母大写,比如Student“UserManager”),和函数 / 变量的蛇形命名法区分;__init__方法:特殊的 “构造方法”,创建实例时自动调用,用于初始化实例的属性。__init__两边各有两个下划线,是 Python 的特殊方法标识,不能写错;self:必须作为__init__和所有方法的第一个参数,代表 “当前实例本身”。通过self.属性名可以给实例绑定属性,通过self.方法名()可以调用实例的其他方法;- 属性:实例的数据(比如学生的
name“age”),通过self.属性名定义; - 方法:实例的操作逻辑(比如
calculate_average“show_info”),本质是封装在类里的函数。
示例:定义Person类(基础类)
先定义一个简单的Person类,包含 “姓名、年龄” 两个属性,以及 “显示信息” 的方法:
python
运行
classPerson:"""人类的基础类,包含姓名和年龄属性,以及显示信息的方法"""def__init__(self,name,age):# 初始化属性:self.name绑定姓名,self.age绑定年龄self.name=name self.age=agedefshow_info(self):"""显示个人基本信息"""print(f"姓名:{self.name},年龄:{self.age}岁")# 打印类的文档字符串(查看类的功能)print(Person.__doc__)# 输出:人类的基础类,包含姓名和年龄属性,以及显示信息的方法2. 创建实例:根据类生成具体对象
创建实例(也叫 “实例化”)的语法很简单:实例名 = 类名(参数1, 参数2, ...),参数要和__init__方法除了self之外的参数对应。
示例:创建Person实例
python
运行
# 创建两个Person实例:小明和小红xiaoming=Person("小明",15)xiaohong=Person("小红",14)# 查看实例的类型(确认是Person类的实例)print(type(xiaoming))# 输出:<class '__main__.Person'>创建实例时,Python 会自动调用__init__方法:
- 执行
Person("小明", 15)时,Python 创建一个空实例; - 自动调用
__init__(xiaoming, "小明", 15)(self就是刚创建的空实例); - 给实例绑定
name="小明"“age=15” 两个属性; - 返回绑定好属性的实例,赋值给
xiaoming。
3. 访问属性和方法:实例的核心操作
创建实例后,通过 “实例名.属性名” 访问属性,通过 “实例名.方法名()” 调用方法(不用传self参数,Python 会自动传递)。
示例:操作Person实例
python
运行
# 1. 访问实例属性print(xiaoming.name)# 输出:小明(访问name属性)print(xiaohong.age)# 输出:14(访问age属性)# 2. 修改实例属性xiaoming.age=16# 修改小明的年龄为16print(f"小明修改后的年龄:{xiaoming.age}岁")# 输出:小明修改后的年龄:16岁# 3. 调用实例方法xiaoming.show_info()# 输出:姓名:小明,年龄:16岁(调用show_info方法)xiaohong.show_info()# 输出:姓名:小红,年龄:14岁这里要注意:调用方法时不用传self参数,Python 会自动把实例本身作为self传入方法,所以方法内部能通过self访问实例的属性和其他方法。
4. 类属性:所有实例共享的属性
除了 “实例属性”(每个实例独有的属性,比如小明的age和小红的age),还有 “类属性”—— 所有实例共享的属性,比如Person类的 “物种” 属性(所有Person实例的物种都是 “人类”)。
类属性直接定义在类的内部、__init__方法之外,通过 “类名.属性名” 或 “实例名.属性名” 访问。
示例:定义和使用类属性
python
运行
classPerson:# 类属性:所有Person实例共享,物种都是“人类”species="人类"def__init__(self,name,age):# 实例属性:每个实例独有的属性self.name=name self.age=agedefshow_info(self):# 方法中访问类属性(self.species 或 Person.species)print(f"物种:{self.species},姓名:{self.name},年龄:{self.age}岁")# 创建实例xiaoming=Person("小明",15)xiaohong=Person("小红",14)# 访问类属性print(Person.species)# 输出:人类(通过类名访问)print(xiaoming.species)# 输出:人类(通过实例名访问)# 调用方法(方法内部访问类属性)xiaoming.show_info()# 输出:物种:人类,姓名:小明,年龄:15岁# 修改类属性(所有实例都会受影响)Person.species="智人"print(xiaohong.species)# 输出:智人(小红的species也变了)关键区别:
- 实例属性:每个实例独有,修改一个实例的属性不影响其他实例;
- 类属性:所有实例共享,修改类属性会影响所有实例。
三、类的进阶:继承与方法重写
当多个类有重复属性和方法时(比如Student和Teacher都有 “姓名、年龄”,都要 “显示信息”),可以用继承减少重复代码。继承让 “子类” 自动拥有 “父类” 的属性和方法,子类只需编写自己特有的属性和方法即可。
1. 继承的基本用法:子类继承父类
继承的语法是class 子类名(父类名):,子类会自动继承父类的所有属性和方法。比如让Student类继承Person类,Student是子类,Person是父类(也叫超类)。
示例:Student类继承Person类
python
运行
# 父类:Person(已定义,包含name、age属性和show_info方法)classPerson:species="人类"def__init__(self,name,age):self.name=name self.age=agedefshow_info(self):print(f"姓名:{self.name},年龄:{self.age}岁")# 子类:Student 继承 PersonclassStudent(Person):"""学生类,继承Person类,新增成绩属性和计算平均分的方法"""def__init__(self,name,age,scores):# 调用父类的__init__方法,初始化name和age属性(避免重复代码)super().__init__(name,age)# 子类特有的属性:成绩列表self.scores=scores# 子类特有的方法:计算平均分defcalculate_average(self):returnsum(self.scores)/len(self.scores)ifself.scoreselse0# 子类重写父类的show_info方法(扩展功能)defshow_info(self):# 先调用父类的show_info方法super().show_info()# 再添加子类特有的信息(平均分)avg=self.calculate_average()print(f"成绩:{self.scores},平均分:{avg:.1f}")# 创建Student实例xiaoming=Student("小明",15,[85,92,76])# 调用子类的方法(继承+重写+新增)xiaoming.show_info()# 输出父类的信息+子类的成绩信息print(f"小明的平均分:{xiaoming.calculate_average():.1f}")# 调用子类新增方法运行结果:
plaintext
姓名:小明,年龄:15岁 成绩:[85, 92, 76],平均分:84.3 小明的平均分:84.3几个关键知识点:
super()函数:在子类的__init__方法中调用super().__init__(name, age),会自动调用父类的__init__方法,初始化父类的属性,避免重复写self.name = name“self.age = age”;- 方法重写:子类可以定义和父类同名的方法(比如
show_info),覆盖父类的方法。在重写时,可通过super().方法名()调用父类的方法,再扩展子类特有的功能; - 子类特有属性 / 方法:子类可以添加父类没有的属性(比如
scores)和方法(比如calculate_average),实现功能扩展。
2. 多继承(可选):子类继承多个父类
Python 支持多继承(一个子类继承多个父类),语法是class 子类名(父类1, 父类2):。但多继承容易导致 “菱形问题”(多个父类有同名方法时的优先级问题),新手建议优先用 “单继承 + 组合”,这里简单示例:
python
运行
# 父类1:Person(姓名、年龄)classPerson:def__init__(self,name,age):self.name=name self.age=age# 父类2:HasScore(成绩相关方法)classHasScore:defcalculate_average(self,scores):returnsum(scores)/len(scores)ifscoreselse0# 子类:Student 继承 Person 和 HasScoreclassStudent(Person,HasScore):def__init__(self,name,age,scores):super().__init__(name,age)self.scores=scoresdefshow_info(self):avg=self.calculate_average(self.scores)# 调用HasScore的方法print(f"姓名:{self.name},年龄:{self.age},平均分:{avg:.1f}")# 创建实例xiaoming=Student("小明",15,[85,92,76])xiaoming.show_info()# 输出:姓名:小明,年龄:15,平均分:84.33. 组合:类之间的 “包含” 关系
除了继承(“是” 的关系,比如Student是Person),类之间还有 “包含” 关系(比如Student包含Course,学生有课程),这时候用组合更合适 —— 把一个类的实例作为另一个类的属性。
示例:Student类包含Course类的实例
python
运行
# 定义Course类(课程)classCourse:def__init__(self,name,score):self.name=name# 课程名self.score=score# 课程成绩defshow_course(self):returnf"{self.name}:{self.score}分"# 定义Student类(包含多个Course实例)classStudent:def__init__(self,name,age):self.name=name self.age=age self.courses=[]# 存储Course实例的列表(组合关系)# 添加课程defadd_course(self,course):self.courses.append(course)# 显示学生和课程信息defshow_info(self):print(f"姓名:{self.name},年龄:{self.age}岁")print("课程成绩:")total_score=0forcourseinself.courses:print(f" -{course.show_course()}")total_score+=course.score avg=total_score/len(self.courses)ifself.courseselse0print(f"平均分:{avg:.1f}")# 创建课程实例math=Course("数学",85)english=Course("英语",92)# 创建学生实例,添加课程xiaoming=Student("小明",15)xiaoming.add_course(math)xiaoming.add_course(english)# 显示信息xiaoming.show_info()运行结果:
plaintext
姓名:小明,年龄:15岁 课程成绩: - 数学:85分 - 英语:92分 平均分:88.5组合比继承更灵活:学生可以添加任意多的课程,课程的修改(比如新增 “课程时长” 属性)不会影响学生类,符合 “低耦合” 的设计思想。
四、综合实操:学生管理系统(类版)
咱们用类重构 “学生管理系统”,实现以下功能:
- 用
Course类存储课程信息(名称、成绩); - 用
Student类存储学生信息(姓名、年龄、课程列表),包含添加课程、计算平均分、显示信息的方法; - 用
StudentManager类管理多个学生实例,包含添加学生、显示所有学生、计算班级平均分的方法。
完整代码
python
运行
# 1. 课程类:存储课程名称和成绩classCourse:def__init__(self,name,score):self.name=name self.score=scoredefget_info(self):"""返回课程的字符串信息"""returnf"{self.name}({self.score}分)"# 2. 学生类:存储学生信息,包含课程列表classStudent:def__init__(self,name,age):self.name=name self.age=age self.courses=[]# 组合:存储Course实例defadd_course(self,course):"""添加课程(接收Course实例)"""ifisinstance(course,Course):# 确保传入的是Course实例self.courses.append(course)print(f"✅ 给{self.name}添加课程:{course.get_info()}")else:print(f"❌ 无效课程:{course}(必须是Course实例)")defcalculate_average(self):"""计算所有课程的平均分"""ifnotself.courses:return0.0total=sum(course.scoreforcourseinself.courses)returnround(total/len(self.courses),1)defshow_detail(self):"""显示学生的详细信息"""print(f"\n【学生信息】")print(f"姓名:{self.name},年龄:{self.age}岁")print(f"课程数量:{len(self.courses)}门")ifself.courses:print("课程列表:")forcourseinself.courses:print(f" -{course.get_info()}")print(f"平均分:{self.calculate_average()}")# 3. 学生管理类:管理多个Student实例classStudentManager:def__init__(self):self.students=[]# 存储Student实例defadd_student(self,student):"""添加学生(接收Student实例)"""ifisinstance(student,Student):self.students.append(student)print(f"\n✅ 添加学生成功:{student.name}")else:print(f"\n❌ 无效学生:{student}(必须是Student实例)")defshow_all_students(self):"""显示所有学生的信息"""print(f"\n===== 所有学生信息(共{len(self.students)}人)=====")ifnotself.students:print("❌ 暂无学生信息")returnfori,studentinenumerate(self.students,start=1):print(f"\n{i}. ",end="")student.show_detail()defcalculate_class_average(self):"""计算班级所有学生的平均分(总平均分)"""ifnotself.students:return0.0total_avg=0.0valid_students=0forstudentinself.students:avg=student.calculate_average()ifavg>0:# 排除没有课程的学生total_avg+=avg valid_students+=1returnround(total_avg/valid_students,1)ifvalid_studentselse0.0# 4. 主程序:使用上述类defmain():print("===== 学生管理系统(类版) =====")# 创建学生管理器manager=StudentManager()# 1. 添加学生1:小明xiaoming=Student("小明",15)# 给小明添加课程xiaoming.add_course(Course("数学",85))xiaoming.add_course(Course("英语",92))xiaoming.add_course(Course("语文",76))# 添加到管理器manager.add_student(xiaoming)# 2. 添加学生2:小红xiaohong=Student("小红",14)xiaohong.add_course(Course("数学",95))xiaohong.add_course(Course("英语",88))manager.add_student(xiaohong)# 3. 显示所有学生manager.show_all_students()# 4. 计算班级平均分class_avg=manager.calculate_class_average()print(f"\n===== 班级总平均分:{class_avg}=====")# 运行主程序if__name__=="__main__":main()运行结果
plaintext
===== 学生管理系统(类版) ===== ✅ 给小明添加课程:数学(85分) ✅ 给小明添加课程:英语(92分) ✅ 给小明添加课程:语文(76分) ✅ 添加学生成功:小明 ✅ 给小红添加课程:数学(95分) ✅ 给小红添加课程:英语(88分) ✅ 添加学生成功:小红 ===== 所有学生信息(共2人)===== 1. 【学生信息】 姓名:小明,年龄:15岁 课程数量:3门 课程列表: - 数学(85分) - 英语(92分) - 语文(76分) 平均分:84.3 2. 【学生信息】 姓名:小红,年龄:14岁 课程数量:2门 课程列表: - 数学(95分) - 英语(88分) 平均分:91.5 ===== 班级总平均分:87.9 =====这个系统完全基于类实现,每个类职责明确:Course管课程,Student管学生,StudentManager管学生集合,后期要添加功能(比如删除学生、修改课程成绩),只需在对应类中添加方法,不用修改其他类,扩展性极强。
五、新手必踩的 5 个坑:避坑指南
类的概念和用法比函数复杂,新手容易在 “self的使用”“继承逻辑”“属性访问” 等方面踩坑,咱们总结 5 个高频坑点:
坑 1:忘记写__init__方法,导致实例没有属性
python
运行
# 错误示例:忘记__init__,实例没有name属性classStudent:# 没有__init__方法,没有绑定name属性defshow_info(self):print(f"姓名:{self.name}")# 创建实例时无法传name参数xiaoming=Student("小明")# 报错:TypeError: Student() takes no arguments解决:必须定义__init__方法,在其中绑定实例属性:
python
运行
classStudent:def__init__(self,name):self.name=name# 绑定name属性defshow_info(self):print(f"姓名:{self.name}")xiaoming=Student("小明")xiaoming.show_info()# 正确输出:姓名:小明坑 2:self参数遗漏或位置错误
python
运行
# 错误示例1:方法中遗漏self参数classStudent:def__init__(self,name):self.name=namedefshow_info():# 遗漏selfprint(f"姓名:{self.name}")xiaoming=Student("小明")xiaoming.show_info()# 报错:TypeError: show_info() takes 0 positional arguments but 1 was given# 错误示例2:self不是第一个参数classStudent:def__init__(name,self):# self位置错误self.name=name# 创建实例时报错:TypeError: __init__() got multiple values for argument 'self'规则:__init__和所有实例方法的第一个参数必须是self,不能遗漏或换位置。
坑 3:继承时忘记调用super().__init__
python
运行
# 错误示例:子类继承父类,但没调用父类的__init__,导致父类属性未初始化classPerson:def__init__(self,name):self.name=nameclassStudent(Person):def__init__(self,name,age):# 忘记调用super().__init__(name),父类的name属性未绑定self.age=age xiaoming=Student("小明",15)print(xiaoming.name)# 报错:AttributeError: 'Student' object has no attribute 'name'解决:子类__init__中必须调用super().__init__(父类参数),初始化父类属性:
python
运行
classStudent(Person):def__init__(self,name,age):super().__init__(name)# 调用父类__init__,绑定nameself.age=age xiaoming=Student("小明",15)print(xiaoming.name)# 正确输出:小明坑 4:混淆实例属性和类属性
python
运行
# 错误示例:修改实例的类属性,误以为会影响所有实例classPerson:species="人类"# 类属性xiaoming=Person()xiaoming.species="外星人"# 实际是给实例添加了一个同名的实例属性xiaohong=Person()print(xiaoming.species)# 输出:外星人(实例属性)print(xiaohong.species)# 输出:人类(类属性,未被修改)原因:给实例赋值 “实例名.类属性名” 时,不会修改类属性,而是给实例添加一个同名的实例属性。修改类属性必须用 “类名.类属性名”:
python
运行
Person.species="外星人"# 修改类属性print(xiaohong.species)# 输出:外星人(所有实例的类属性都变了)坑 5:方法调用时传了self参数
python
运行
# 错误示例:调用方法时手动传self参数classStudent:def__init__(self,name):self.name=namedefshow_info(self):print(f"姓名:{self.name}")xiaoming=Student("小明")xiaoming.show_info(xiaoming)# 错误:手动传了self参数规则:调用实例方法时,Python 会自动把实例作为self传入,不用手动传self,正确写法是xiaoming.show_info()。
六、小结与下一篇预告
这篇你学到了什么?
- 类的基础:用
class定义类,__init__方法初始化属性,self代表实例本身,创建实例并访问属性 / 方法; - 属性类型:实例属性(每个实例独有)和类属性(所有实例共享)的区别与用法;
- 继承:子类继承父类,用
super()调用父类方法,重写父类方法扩展功能; - 组合:类之间的 “包含” 关系,把一个类的实例作为另一个类的属性,灵活关联对象;
- 实战应用:用多个类协作实现学生管理系统,每个类职责明确,代码可扩展;
- 避坑要点:
self的使用、继承时super()的调用、实例属性与类属性的区别。
下一篇预告
今天的类让我们能封装对象的属性和方法,但当需要处理 “大量实例” 或 “复杂数据” 时,比如读取文件中的学生信息、将数据保存到 Excel,还需要结合 “文件操作” 和 “第三方库”。下一篇咱们会学 Python 的 “文件操作”,包括读取文本文件、写入文件、处理 CSV 数据,让程序能持久化存储数据(关闭程序后数据不丢失),为后面的实战项目打下数据存储基础。
如果这篇内容帮你掌握了类,欢迎在评论区分享你的 “类设计”(比如设计一个Book类或GameRole类),咱们一起交流进步~