类与对象
前面我们已经学了变量、流程控制和函数,也在函数化学生信息管理系统中用字典保存学生信息,再用函数处理这些数据。
这种写法可以完成任务。不过,当学生越来越多、功能越来越复杂时,数据和处理数据的函数会逐渐分散。如何让同一类数据保持统一结构,并让处理这些数据的功能与数据放在一起?
本节继续使用“学生成绩”场景,对比面向过程和面向对象两种写法,认识类与对象。
需求:用同一组学生成绩数据完成信息展示,并比较两种代码组织方式的差异。
功能分析:先理解对象如何保存数据和提供功能,再用 Student 类创建对象,观察数据与方法如何组合。
面向对象模型
面向对象概念
面向对象的英文是 Object-Oriented Programming,简称 OOP。
先不用急着背定义,可以把它理解成:
把现实中的事物抽象成程序里的对象,再让对象完成自己的事情。
例如:
- 一个人
- 一本书
- 一辆车
- 一个学生
这些事物都可以抽象成对象。
认识对象,可以从两个角度入手。
对象抽象
以“人”为例,每个人都有自己的信息:
- 姓名
- 年龄
- 身高
- 体重
人还可以完成一些行为:
- 吃饭
- 睡觉
- 跑步
程序里的对象也是如此:它既可以保存数据,也可以提供处理这些数据的功能。
属性与方法
对象里通常会放两类内容:
- 属性:表示对象的数据状态
- 方法:表示对象能够完成的操作
例如,一个学生对象可以包含:
- 属性:姓名、成绩
- 方法:打印成绩、修改成绩
可以先简单记成:
对象把相关的数据和功能放到了一起。
类的抽象与实例化
类是同一类事物的抽象模板,对象是根据这个模板创建出来的具体个体。
例如:
类:人
对象:正心、小明、小红同一个类可以创建多个对象,每个对象又可以保存自己的数据。
可以这样区分:
- 类:描述这一类事物共同具有的特征和行为
- 对象:按照类创建出来的具体某一个
编程组织方式对比
这两个概念比较抽象,先从关注点来区分。
面向过程:执行步骤
面向过程会把完成任务的步骤依次写出来,关注的是:
先做什么,再做什么,最后做什么。
面向对象:角色职责
面向对象会先划分角色和职责,再让不同对象完成自己的任务,关注的是:
谁来做这件事。
可以先这样记:
- 面向过程:关注“怎么做”
- 面向对象:关注“谁来做”
面向对象并不会替代面向过程。对象的方法内部仍然要一步步执行代码,只是数据和功能的组织方式不同。
面向对象特征
面向对象通常会涉及三个特点:
- 封装
- 继承
- 多态
后面的课程会逐步展开。本节先理解“封装”:把相关的数据和功能组织到一起。
案例:学生成绩对象建模
在 Python 里,所有数据都可以看成对象。我们自己定义一个类,相当于定义一种新的对象类型。
接下来使用同一组学生数据,对比两种代码组织方式。
需求:分别使用字典与函数、类与对象打印两名学生的成绩,比较两种写法中数据与处理功能的组织方式。
功能分析:先用字典保存数据并由外部函数处理,再定义 Student 类,把姓名、成绩和打印成绩的方法组合到对象中。
方案一:字典与函数
先使用字典保存两名学生的数据:
student1 = {"name": "小明", "score": 98}
student2 = {"name": "小红", "score": 81}再定义一个函数,用来打印学生成绩:
def print_score(student):
print(f"姓名:{student['name']} 成绩:{student['score']}")
print_score(student1)
print_score(student2)运行结果:
姓名:小明 成绩:98
姓名:小红 成绩:81这种写法没有问题,小项目里完全可以使用。
不过,字典只负责保存数据,函数单独放在外面。调用函数时,还要主动把对应的学生数据传进去。
方案二:类与对象
换成面向对象思路,我们会先思考:
“学生”是否应该有一个统一模板,并且由学生自己打印成绩?
这个模板需要规定两类内容:
- 学生具有姓名和成绩
- 学生可以打印自己的成绩
于是可以抽象出一个 Student 类。
类定义
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print(f"姓名:{self.name} 成绩:{self.score}")这里先认识代码的整体结构:
class用来定义类Student是类名,通常使用大驼峰命名__init__()用来初始化对象self.name、self.score是对象自己的属性print_score()是对象的方法
对象实例化
类定义好后,就可以根据类创建具体对象:
student1 = Student("小明", 98)
student2 = Student("小红", 81)这一步叫作实例化。也就是根据 Student 模板,创建两个具体的学生对象。
属性与方法调用
可以直接访问对象的属性,也可以让对象调用自己的方法:
print(f"姓名:{student1.name}")
print(f"成绩:{student1.score}")
student1.print_score()
student2.print_score()运行结果:
姓名:小明
成绩:98
姓名:小明 成绩:98
姓名:小红 成绩:81观察这段代码:
student1和student2都来自Student类- 两个对象分别保存自己的姓名和成绩
- 调用
student1.print_score()时,打印的是student1自己的数据
这样一来,学生数据和打印成绩的方法就组织到了一起,它们之间的关系也更清楚。
下一节继续
这里先看类和对象的完整形态。__init__()、self 和实例属性的具体工作过程,会在下一节继续拆解。
类与对象速查
先记住三个步骤:
- 抽象出类
- 根据类创建对象
- 让对象完成自己的工作
对应到学生成绩案例:
| 代码 | 作用 |
|---|---|
Student | 学生类,表示统一模板 |
student1、student2 | 根据类创建的对象 |
print_score() | 对象可以调用的方法 |
函数主要封装完成任务的步骤,类和对象还可以把数据与处理数据的方法组织到一起。当项目中的数据和功能越来越多时,这种组织方式可以让它们的关系更清晰,也更容易继续扩展。
小结
本节重点掌握四件事:
- 类是一类事物的抽象模板
- 对象是根据类创建出来的具体实例
- 对象通常包含属性和方法
- 面向对象的核心是把相关的数据和功能组织到一起
练习
- 自己举出 3 组“类”和“对象”的例子
- 用字典表示一个学生,再思考它和
Student对象有什么区别 - 尝试编写一个
Dog类,保存小狗的名字,并提供一个“叫”的方法