查看全课大纲(第 31 / 51 节)
- 1.Python 是什么?为什么值得学?
- 2.如何为 Python 编程准备一个“全家桶”环境?
- 3.Python 有哪几种执行方法?
- 4.行与缩进到底有什么用?
- 5.注释有什么用,怎么写?
- 6.变量:如何给数据起个好名字?
- 7.运算符的优先级和常见坑点是什么?
- 8.Python 中,数字和布尔值究竟有哪几种?
- 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
- 10.如何将程序的结果展示给用户或保存下来?
- 11.CSV 文件怎么读怎么写才方便?
- 12.列表是什么,如何创建和访问它?
- 13.列表的修改操作,有哪些方法?
- 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
- 15.判断语句:如何让程序根据条件做不同的事?
- 16.for 循环如何遍历序列中的元素?
- 17.while 循环什么时候才会停?
- 18.while 循环的结束条件,你写对了吗?
- 19.循环中途想提前结束,怎么办?
- 20.`break` 和 `continue` 到底有什么区别?
- 21.列表推导式如何让代码更简洁高效?
- 22.字符串怎么定义、拼接和操作?
- 23.元组:为什么定义单个元素时要加逗号?
- 24.集合(set)怎么用?如何快速去重和做集合运算?
- 25.字典是什么,为什么比列表更方便?
- 26.字典的增删改查与遍历,如何避免 KeyError?
- 27.函数里的变量,为什么有的能用有的不能用?
- 28.在函数内部修改全局变量,为什么有时不生效?
- 29.函数里能修改全局变量吗?返回值怎么用?
- 30.匿名函数:如何用 lambda 快速定义简单函数?
- 31.面向对象编程是什么,为什么需要它?
- 32.类中的方法和属性到底怎么定义和使用?
- 33.如何控制对对象内部属性和方法的访问?
- 34.异常发生时,程序会怎样继续执行?
- 35.如何组织代码:包和模块
- 36.`if __name__ == '__main__'` 到底有什么用?
- 37.如何用 IDE 的 Debug 功能排查程序错误?
- 38.Matplotlib 绘图的第一步该做什么?
- 39.Figure 是什么,如何设置图形尺寸?
- 40.如何自定义 Matplotlib 的坐标轴?
- 41.如何为图表添加图例,让不同曲线一目了然?
- 42.饼图怎么画才能直观展示数据占比?
- 43.如何用 Matplotlib 绘制散点图?
- 44.柱状图怎么画?
- 45.如何使用 Matplotlib 绘制等高线图?
- 46.如何在 Matplotlib 中绘制子图?
- 47.zip 函数如何把多个列表“打包”在一起?
- 48.如何给可迭代对象加上索引?
- 49.random 模块如何生成随机数?
- 50.math 模块怎么用?
- 51.Counter 如何快速统计元素出现次数?
面向对象编程是什么,为什么需要它?
约 10 分钟
小象实战讲义 · Python 编程从入门到实践
在之前的课程中,我们主要使用函数来组织代码逻辑。但当程序变得复杂,需要管理多个具有相似属性和行为的“事物”时,函数式编程会显得力不从心。本节将带你认识一种更强大的代码组织方式——面向对象编程(OOP),理解其核心概念“类”与“对象”,并学会如何用它们来更清晰地管理数据和逻辑。这是进入 Python 高级知识领域的重要一步。
💡 核心导读
- 面向对象编程的动机:通过一个管理多只小狗状态的传统函数式代码示例,揭示其可读性差、易出错、难扩展的问题。
- 面向对象的核心概念:理解“类”作为事物模板、“对象”作为具体实例的关系。
- 类的定义与使用:掌握如何定义一个类,以及如何创建和使用对象。
- 对象的数据与行为:把每只小狗的名字和状态保存为对象自己的数据,把啃骨头、喝水等动作写成对象自己的方法。
从函数式编程的痛点说起
假设我们需要编写一个程序来模拟给小狗发指令:小狗可以“啃骨头”和“喝水”,并且每只小狗有自己的名字和状态(比如是否啃过骨头、是否喝过水)。
一种直观但繁琐的实现方式是使用多个列表和函数:
# 使用列表和函数管理小狗状态
name_list = ['旺财', '来福']
done_list = [0, 0] # 0:无事可做,1:已啃骨头,2:已喝水
def eat(name):
print("{}正在啃骨头,休息几秒钟后喝水...".format(name))
def drink(name):
print("{}小狗正在喝水".format(name))
def action(index):
if done_list[index] == 0:
done_list[index] = 1
return eat(name_list[index])
elif done_list[index] == 1:
done_list[index] = 2
return drink(name_list[index])
else:
print("{}准备好了!".format(name_list[index]))
# 调用示例
action(0) # 对旺财发起指令
action(1) # 对来福发起指令
action(0) # 再次对旺财发起指令
action(0) # 第三次对旺财发起指令旺财正在啃骨头,休息几秒钟后喝水...
来福正在啃骨头,休息几秒钟后喝水...
旺财小狗正在喝水
旺财准备好了!这段代码虽然能运行,但存在几个明显问题:
- 数据分散:小狗的名字和状态分散在两个独立的列表(
name_list,done_list)中,管理困难。 - 可读性差:调用
action(0)时,如果不看代码,很难知道0代表哪只小狗。 - 扩展性差:如果想增加一只小狗,必须同时修改
name_list和done_list,且要确保它们的索引对应。如果状态更复杂,需要管理的列表会更多,极易出错。
面向对象编程正是为了解决这类问题而生的。
面向对象的核心:类与对象
面向对象编程(OOP) 是一种将软件系统中相似的操作逻辑、数据和状态,以“类”的形式组织起来,并通过创建“对象”实例来复用的设计和编程方法。
- 类(Class):一个事物的抽象模板,定义了一类事物共有的属性(数据)和方法(行为)。例如,“小狗”这个类可以定义属性“名字”和“状态”,以及方法“啃骨头”、“喝水”。
- 对象(Object):通过类创建出来的具体实例。每个对象都拥有类中定义的属性和方法,但各自的状态(属性值)可以不同。例如,根据“小狗”类可以创建出名叫“旺财”和“来福”的两个具体小狗对象。
关系:类是创建对象的蓝图(模板),对象是根据蓝图制造出的具体产品。
定义类与创建对象
让我们用面向对象的方式重写小狗的例子。
# 定义一个Dog类
class Dog:
# 构造方法,在创建对象时自动调用,用于初始化属性
def __init__(self, name):
self.name = name # 实例属性:小狗的名字
self.done = 0 # 实例属性:小狗的状态,0-无事,1-已啃骨,2-已喝水
# 公开方法:啃骨头
def eat(self):
print("{}正在啃骨头,休息几秒钟后喝水...".format(self.name))
# 公开方法:可以在类外部调用
def drink(self):
print("{}小狗正在喝水".format(self.name))
# 公开方法:对外提供的指令接口
def action(self):
if self.done == 0:
self.done = 1
return self.eat() # 内部调用自己的方法
elif self.done == 1:
self.done = 2
return self.drink()
else:
print("{}准备好了!".format(self.name))
# 创建对象:根据Dog类模板,实例化具体的小狗
wang_cai = Dog('旺财') # wang_cai 是一个Dog类的对象
laifu = Dog('来福') # laifu 是另一个Dog类的对象
# 对对象进行操作
wang_cai.action()
laifu.action()
wang_cai.action()
wang_cai.action()旺财正在啃骨头,休息几秒钟后喝水...
来福正在啃骨头,休息几秒钟后喝水...
旺财小狗正在喝水
旺财准备好了!代码解析:
class Dog::定义一个名为Dog的类。类名通常采用“大驼峰”命名法(每个单词首字母大写)。def __init__(self, name)::这是构造方法。当使用Dog('旺财')创建对象时,Python 会自动调用它。self参数代表当前正在创建的对象实例本身,必须是方法的第一个参数。name是创建对象时传入的参数。self.name = name:将传入的name赋值给当前对象的name属性。这样,每个小狗对象就拥有了自己独立的名字。wang_cai = Dog('旺财'):这行代码做了两件事:a) 在内存中创建一个新的Dog对象;b) 调用__init__方法初始化这个对象,并将其赋值给变量wang_cai。wang_cai.action():通过对象调用其方法。此时,方法内的self会自动指向wang_cai这个对象,因此self.name就是“旺财”,self.done也是旺财自己的状态。
现在,每只小狗的数据(名字、状态)和行为(action 逻辑)都被封装在各自的对象里。添加新小狗只需 Dog('新名字'),代码清晰且不易出错。
📝 动手练一练
👉 点击查看参考答案
题目:请定义一个 Student 类。
- 属性包括:
name(姓名)、score(分数)。 - 方法包括:
__init__(self, name, score):初始化姓名和分数。get_grade(self):根据分数返回等级(≥90为‘A’,≥80为‘B’,≥70为‘C’,<70为‘D’)。show_info(self):打印学生的姓名和等级。
- 创建两个学生对象(如“张三”, 85 和 “李四”, 92),并调用
show_info方法。
思路:仿照 Dog 类的结构,将学生相关的数据和逻辑封装在一起。
参考答案:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 80:
return 'B'
elif self.score >= 70:
return 'C'
else:
return 'D'
def show_info(self):
grade = self.get_grade()
print("学生{}的成绩等级是:{}".format(self.name, grade))
# 创建对象并测试
stu1 = Student("张三", 85)
stu2 = Student("李四", 92)
stu1.show_info()
stu2.show_info()学生张三的成绩等级是:B
学生李四的成绩等级是:A常见错误
| 错误写法 | 现象或报错 | 原因 | 正确写法 |
|---|---|---|---|
def my_method(): (在类中定义方法时漏写self) | TypeError: MyClass.my_method() takes 0 positional arguments but 1 was given(MyClass 为示例类名) | 类的方法在调用时,Python会自动传入调用该方法的对象本身作为第一个参数。如果定义时没有预留self参数,就会导致参数数量不匹配。 | def my_method(self): |
class dog: (类名用小写) | 无语法错误,但不符合约定 | Python 社区约定类名使用“大驼峰命名法”(CamelCase),以提高代码可读性,一眼就能区分类和普通变量/函数。 | class Dog: |
在类的方法内部访问属性时忘记加self. | NameError: name 'xxx' is not defined 或 逻辑错误(使用了局部变量或全局变量) | 在类的方法中,要访问属于当前对象的属性或调用其他方法,必须通过self.来引用。否则Python会在局部或全局作用域中查找,通常找不到。 | 确保访问属性时使用self.属性名,调用方法时使用self.方法名()。 |
小结
- 面向对象编程(OOP) 通过“类”和“对象”来组织代码,将数据(属性)和操作数据的方法封装在一起,提高了代码的复用性、可读性和可维护性。
- 类是创建对象的蓝图或模板,定义了同类对象共有的属性和方法。
- 对象是类的具体实例,拥有类中定义的属性和方法,并且每个对象的状态独立。
- 使用
class ClassName:来定义一个类。类中的方法第一个参数通常是self,代表对象实例本身。 __init__(self, ...)是构造方法,在创建对象时自动调用,用于初始化对象的属性。- 创建对象的语法是
对象名 = 类名(参数);同一个类可以创建多个互相独立的对象。
📋 行动清单
- 将本节“动手练一练”的
Student类代码完整敲一遍并运行,确保理解每个部分的作用。 - 为
Student类增加一个update_score(self, new_score)方法,用来修改分数,修改后重新调用show_info查看等级变化。 - 回顾开头的函数式小狗管理代码,思考如果增加“小狗品种”和“是否打过疫苗”两个状态,用面向对象的方式改写会比用函数式简单多少。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第4章代码包:面向对象基础(含1 个 Notebook)ZIP · 2KB