从零学 Python 系列(八):面向对象编程入门,用 “类” 构建更优雅的代码
在前几篇中,我们用 “函数 + 数据” 的方式写程序,这种方式适合简单任务,但面对复杂项目(比如开发一个电商系统,需要管理用户、商品、订单等多种实体)时,会遇到两个问题:一是数据和操作数据的函数分散在各处,难以维护;二是新增功能时,容易影响已有代码。这篇就带你学习 “面向对象编程(OOP)”—— 一种把 “数据” 和 “操作数据的函数” 封装在一起的思想,让代码更像现实世界的逻辑,更易扩展和复用。
一、面向对象的核心概念:类与对象
先理解两个最基础的概念,用生活中的例子类比:
-** 类(Class):是 “模板” 或 “蓝图”,描述某一类事物的共同特征和行为。比如 “学生类”,共同特征(属性)有 “姓名、年龄、成绩”,共同行为(方法)有 “上课、考试”。- 对象(Object)**:是 “类的实例”,根据类创建的具体个体。比如根据 “学生类” 创建 “张三”“李四” 这两个具体的学生对象,每个对象有自己的属性值(张三年龄 18,李四年龄 19)。
类比: 类就像 “汽车设计图纸”,对象就像根据图纸造出来的 “具体汽车”(张三的车、李四的车),图纸规定了汽车必须有轮子、能行驶(共同特征和行为),但每辆车的颜色、品牌可以不同(属性值不同)。
二、定义类:用 class 关键字创建 “模板”
在 Python 中,用class关键字定义类,格式如下:
class 类名:
# 属性(数据)
# 方法(操作数据的函数)
1. 最简单的类:包含属性和方法
以 “学生类(Student)” 为例,包含属性(姓名、年龄、成绩)和方法(显示信息、考试得分)。
class Student:
# 初始化方法:创建对象时自动调用,用来设置属性
def __init__(self, name, age, score=0):
# self代表“当前对象”,self.属性名 表示对象的属性
self.name = name # 姓名属性
self.age = age # 年龄属性
self.score = score # 成绩属性(默认0)
# 方法:显示学生信息
def show_info(self):
print(f"姓名:{self.name},年龄:{self.age},成绩:{self.score}")
# 方法:考试得分(修改成绩属性)
def get_score(self, points):
self.score += points # 成绩增加points分
print(f"{self.name}考试得了{points}分,现在成绩是{self.score}")
-__init__方法:特殊的 “初始化方法”(构造函数),创建对象时必须调用,用来初始化属性。self是必须的第一个参数,代表当前对象。-** 方法 **:类中的函数,第一个参数必须是self,通过self.属性访问对象的属性。
2. 创建对象:用类实例化具体个体
定义类后,通过 “类名 (参数)” 创建对象(实例化),参数对应__init__方法中的参数(除了 self)。
# 创建学生对象(实例化Student类)
stu1 = Student("张三", 18) # 成绩用默认值0
stu2 = Student("李四", 19, 80) # 成绩设为80
# 调用对象的方法
stu1.show_info() # 输出:姓名:张三,年龄:18,成绩:0
stu2.show_info() # 输出:姓名:李四,年龄:19,成绩:80
# 调用方法修改属性
stu1.get_score(90) # 输出:张三考试得了90分,现在成绩是90
stu2.get_score(5) # 输出:李四考试得了5分,现在成绩是85
# 直接访问对象的属性(不推荐,最好通过方法操作)
print(f"{stu1.name}的最新成绩:{stu1.score}") # 输出:张三的最新成绩:90
优势体现:每个对象的属性独立存储,调用方法时只影响当前对象(stu1 的成绩变化不会影响 stu2),数据和操作更 “内聚”。
三、面向对象的三大特性:封装、继承、多态
面向对象编程有三大核心特性,掌握它们能让代码更灵活、更易扩展。
1. 封装:隐藏内部细节,只暴露接口
封装就是 “把数据和操作数据的方法包装在类里”,并控制外部对内部属性的访问(比如不允许直接修改成绩,必须通过考试得分的方法)。
示例:限制成绩只能通过方法修改
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.__score = 0 # 用双下划线开头的属性是“私有属性”,外部不能直接访问
# 提供方法访问私有属性
def get_score(self):
return self.__score
# 提供方法修改私有属性(可以在方法里加验证)
def add_score(self, points):
if points < 0:
print("错误:分数不能为负数!")
return
self.__score += points
print(f"{self.name}的成绩增加了{points}分,现在是{self.__score}")
# 测试
stu = Student("张三", 18)
print("当前成绩:", stu.get_score()) # 输出:0
# 尝试直接修改私有属性(会失败)
stu.__score = -50 # 这行代码不会报错,但实际上修改的是一个新的公有属性,不是私有属性
print("直接修改后(错误方式):", stu.get_score()) # 输出:0(私有属性没变)
# 通过方法修改(正确方式)
stu.add_score(80) # 输出:张三的成绩增加了80分,现在是80
stu.add_score(-10) # 输出:错误:分数不能为负数!
print("最终成绩:", stu.get_score()) # 输出:80
-** 私有属性 :用__属性名定义,外部不能直接访问或修改,只能通过类内部的方法操作,保证数据安全。- 封装的意义 **:比如成绩不能为负,这个规则封装在add_score方法里,外部不用关心实现细节,只需调用方法即可,减少错误。
2. 继承:子类复用父类的代码
继承允许 “子类(派生类)” 继承 “父类(基类)” 的属性和方法,还能新增自己的属性和方法,或重写父类的方法,实现 “代码复用”。
示例:学生类派生出小学生类和大学生类
# 父类:Student
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def study(self):
print(f"{self.name}在学习")
# 子类:Pupil(小学生),继承自Student
class Pupil(Student):
# 新增属性:grade(年级)
def __init__(self, name, age, grade):
# 调用父类的__init__方法,复用代码
super().__init__(name, age)
self.grade = grade
# 重写父类的study方法
def study(self):
print(f"{self.name}({self.grade}年级小学生)在学习语文和数学")
# 新增方法:做早操
def do_morning_exercise(self):
print(f"{self.name}在做早操")
# 子类:CollegeStudent(大学生),继承自Student
class CollegeStudent(Student):
def __init__(self, name, age, major):
super().__init__(name, age)
self.major = major # 新增属性:专业
# 重写父类的study方法
def study(self):
print(f"{self.name}({self.major}专业大学生)在学习专业课程")
# 测试
pupil = Pupil("小明", 10, 4)
pupil.study() # 输出:小明(4年级小学生)在学习语文和数学
pupil.do_morning_exercise() # 输出:小明在做早操
college = CollegeStudent("小红", 20, "计算机")
college.study() # 输出:小红(计算机专业大学生)在学习专业课程
-** 继承语法 :class 子类名(父类名):-super()函数 **:在子类中调用父类的方法(比如super().__init__()调用父类的初始化方法),避免代码重复。-** 重写(Override)**:子类定义和父类同名的方法,会覆盖父类的方法,实现子类特有的逻辑。
3. 多态:同一操作,不同表现
多态指 “不同对象调用同名方法时,表现出不同的行为”,这是继承的自然结果,能让代码更灵活。
示例:用统一的方式调用不同子类的方法
# 定义一个函数,接收Student类型的对象(或其子类对象)
def let_study(student):
student.study() # 调用study方法,具体行为由对象类型决定
# 创建不同的对象
pupil = Pupil("小明", 10, 4)
college = CollegeStudent("小红", 20, "计算机")
# 调用同一个函数,传入不同对象,表现出不同行为
let_study(pupil) # 输出:小明(4年级小学生)在学习语文和数学
let_study(college) # 输出:小红(计算机专业大学生)在学习专业课程
多态的好处是:函数let_study不需要关心传入的是 “小学生” 还是 “大学生”,只需调用study方法即可,新增 “中学生” 类时,无需修改let_study函数,符合 “开闭原则”(对扩展开放,对修改关闭)。
四、实战:用面向对象重构学生管理系统
把之前的 “学生信息管理系统” 用面向对象的方式重构,用Student类表示学生,StudentManager类管理学生集合(添加、查询、保存等),让代码结构更清晰。
完整代码:
import csv
# 学生类:封装学生的属性和方法
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
# 把对象转换为字典(方便保存到CSV)
def to_dict(self):
return {
"name": self.name,
"age": self.age,
"score": self.score
}
# 学生管理类:封装管理学生的功能
class StudentManager:
def __init__(self, filename):
self.filename = filename # 数据文件路径
self.students = [] # 存储学生对象的列表
self.load_students() # 初始化时加载数据
# 加载学生数据(从CSV)
def load_students(self):
try:
with open(self.filename, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
# 创建Student对象并添加到列表
student = Student(
name=row["name"],
age=int(row["age"]),
score=float(row["score"])
)
self.students.append(student)
print(f"已加载{len(self.students)}条学生数据")
except FileNotFoundError:
print(f"数据文件不存在,将创建新文件:{self.filename}")
# 保存学生数据(到CSV)
def save_students(self):
with open(self.filename, "w", encoding="utf-8", newline="") as f:
fieldnames = ["name", "age", "score"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
# 调用每个学生对象的to_dict方法
writer.writerows([s.to_dict() for s in self.students])
print(f"已保存{len(self.students)}条学生数据")
# 添加学生
def add_student(self, name, age, score):
# 检查是否重名
for s in self.students:
if s.name == name:
print(f"错误:学生{name}已存在!")
return
# 创建新学生对象并添加
new_student = Student(name, age, score)
self.students.append(new_student)
print(f"已添加学生:{name}")
# 显示所有学生
def show_all(self):
if not self.students:
print("暂无学生数据")
return
print("所有学生信息:")
for i, s in enumerate(self.students, 1):
print(f"{i}. 姓名:{s.name},年龄:{s.age},成绩:{s.score}")
# 主程序
def main():
manager = StudentManager("students_oop.csv")
while True:
print("\n=== 面向对象版学生管理系统 ===")
print("1. 添加学生")
print("2. 显示所有学生")
print("3. 保存并退出")
choice = input("请选择操作:")
if choice == "1":
name = input("请输入姓名:")
try:
age = int(input("请输入年龄:"))
score = float(input("请输入成绩:"))
manager.add_student(name, age, score)
except ValueError:
print("输入错误:年龄和成绩必须是数字!")
elif choice == "2":
manager.show_all()
elif choice == "3":
manager.save_students()
print("退出系统,再见!")
break
else:
print("输入错误,请重新选择")
if __name__ == "__main__":
main()
重构后的优势:
- 数据和操作分离:
Student类只关心单个学生的属性,StudentManager类只关心学生集合的管理,职责清晰; - 可扩展性强:如果需要给学生添加 “性别” 属性,只需修改
Student类的__init__和to_dict方法,不影响管理逻辑; - 代码复用率高:比如
to_dict方法封装了对象转字典的逻辑,避免在保存数据时重复写转换代码。
下一篇预告:Python 常用库实战,解锁数据分析与自动化
学会了面向对象编程,你已经具备开发复杂项目的基础了。但 Python 的强大之处在于丰富的第三方库 —— 用几行代码就能实现数据分析、自动化办公、爬虫等高级功能。下一篇会带你实战几个最常用的库:pandas(数据分析)、openpyxl(操作 Excel)、requests(网络请求),让你快速解锁 Python 的实用技能,解决实际问题。
如果这篇的面向对象知识让你对代码结构有了新的理解,欢迎在评论区分享你的实践:比如你用类封装了什么功能,或者对继承、多态有什么疑问,我们一起交流~
更多推荐


所有评论(0)