Python 系列(三十三) 什么是类和对象?零基础理解 Python class
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
如果每天都在重复执行相同的工作,我们能不能让计算机帮我们完成?
前面我们学习了 Python 的变量、数据类型、条件判断、循环,以及列表、字典等知识。现在,我们已经能够编写一些简单的程序,保存数据、进行计算,并根据不同的条件执行不同的操作。
但是,随着程序越来越复杂,一个新的问题也会逐渐出现:如果需要管理大量具有相同特征的事物,怎样才能让代码更容易理解和维护?
例如,作为 Linux 运维工程师,如果需要管理 100 台服务器,每台服务器都有自己的名称、IP 地址和 CPU 使用率,我们应该怎样组织这些信息?
这篇文章就从这个实际问题出发,带你一步步认识 Python 中的类和对象,理解
class、__init__()和self的作用。不需要任何面向对象编程基础,我们从最简单的代码开始。
从一个实际问题开始
假设你是一名 Linux 运维工程师,需要管理公司里的多台 Linux 服务器。
每台服务器都有自己的基本信息,例如:
- 服务器名称,例如
web01。 - IP 地址,例如
192.168.1.10。 - CPU 使用率,例如
75%。
如果只有一台服务器,我们可以使用变量保存这些信息。
1 | server_name = "web01" |
这里先回顾一下变量。
你可以把变量暂时理解成一个带有名称的存储位置。我们给它一个名称,再把需要保存的数据交给它。
例如:
1 | server_name = "web01" |
等号右侧的 "web01" 是需要保存的数据,等号左侧的 server_name 是我们为这份数据起的名称。
以后需要使用服务器名称时,就可以通过 server_name 获取它。
运行上面的程序,输出如下:
1 | web01 |
对于一台服务器来说,这样写没有任何问题。
但如果服务器数量增加到三台呢?
1 | server1_name = "web01" |
你会发现,每增加一台服务器,我们就需要再定义几个变量。
如果公司有 100 台服务器,就需要管理几百个变量。如果以后还要增加内存使用率、磁盘使用率、运行状态等信息,代码还会继续增长。
这时候,问题就出现了:能不能把属于同一台服务器的信息组织在一起,而不是让它们分散在许多变量中?
先用字典组织服务器信息
在学习类之前,我们其实已经掌握了一种解决这个问题的方法,那就是字典。
如果你已经学习过 Python 字典,可以先回忆一下:字典可以通过一个名称,也就是键,找到对应的数据。
例如,我们可以这样保存一台服务器的信息:
1 | server = { |
运行结果:
1 | web01 |
这里的 server 是一个字典。
字典内部保存了三组信息:
| 键 | 对应的值 |
|---|---|
name | web01 |
ip | 192.168.1.10 |
cpu_usage | 75 |
例如,server["name"] 表示从字典中取出键为 "name" 的数据,因此得到 web01。
现在,我们可以用一个字典表示一台服务器,再用列表保存多台服务器的信息。
1 | servers = [ |
运行结果:
1 | 服务器:web01,IP:192.168.1.10,CPU:75% |
如果你暂时还不熟悉列表、字典或者 for 循环,也不用担心。这里最重要的是观察:我们已经可以把每台服务器的相关信息放在一起,并且统一处理多台服务器。
那么,既然字典和列表已经能够保存服务器信息,为什么还要学习类?
当数据和操作需要一起组织时
想象一下,我们的服务器管理程序继续发展。
除了保存服务器名称、IP 地址和 CPU 使用率,我们还希望执行以下操作:
- 显示服务器的基本信息。
- 检查 CPU 使用率是否过高。
- 输出服务器的运行状态。
- 以后可能增加更多与服务器有关的操作。
如果只使用字典,我们当然也能完成这些工作。但是,随着程序越来越复杂,保存数据的代码和处理这些数据的操作可能会散落在程序的不同位置。
例如,检查 CPU 使用率的代码可能需要反复编写:
1 | if server["cpu_usage"] >= 90: |
这段代码本身没有问题。字典也完全可以用于很多实际项目。
不过,如果我们希望把一类事物具有的基本信息,以及围绕这些信息执行的操作,统一组织起来,就可以考虑使用 Python 的类。
我们可以先定义一份统一的规则:
- 每台服务器都需要有名称。
- 每台服务器都需要有 IP 地址。
- 每台服务器都可以显示自己的信息。
- 每台服务器都可以检查自己的 CPU 使用率。
然后,根据这份规则创建不同的服务器,让每台服务器保存自己的数据。
这就是我们接下来要学习的内容。
什么是类
在 Python 中,类(Class)是一种组织相关数据和操作的方式。
对于刚开始学习编程的人,可以先把类理解成一份统一的设计方案。
例如,我们想创建多名学生,每名学生都有姓名和年龄。
我们可以先定义一份学生的基本规则,然后根据这份规则创建 Alice、Bob 等不同的学生对象。
flowchart TD
A["Student 类"] --> B["规定学生对象的基本结构"]
B --> C["创建 Alice 对象"]
B --> D["创建 Bob 对象"]
B --> E["创建其他学生对象"]
C --> F["姓名:Alice<br/>年龄:20"]
D --> G["姓名:Bob<br/>年龄:22"]这里出现了两个重要概念。
类(Class)
类用于定义一类事物的基本结构,以及可以执行的操作。
例如,Student 类可以用来描述学生对象应该保存哪些信息,以及这些对象能够执行哪些操作。
对象(Object)
对象是根据类创建出来的具体实例。
例如,Alice 和 Bob 是两个不同的学生,我们可以分别创建两个对象来保存他们的信息。
可以用生活中的例子帮助理解:
- 类就像一份设计图纸。
- 对象就像根据图纸实际建造出来的房子。
- 同一份图纸可以用来建造多栋房子,但每栋房子都可以有自己的具体情况。
需要注意,这只是帮助理解的比喻。真正编写 Python 程序时,我们需要通过代码定义类,再通过类创建对象。
接下来,我们就动手操作。
使用 class 定义一个类
Python 使用 class 关键字定义类。
先创建一个最简单的类:
1 | class Student: |
先不要急着记住所有术语,我们逐行来看。
1 | class Student: |
class 是 Python 的关键字,用来告诉 Python:接下来要定义一个类。
Student 是我们为这个类起的名称。
在 Python 中,类名通常采用大驼峰命名法,也就是每个单词的首字母大写。例如:
1 | class Student: |
这些名称都符合常见的 Python 类命名习惯。
再看下面这一行:
1 | pass |
pass 是一个占位语句,表示这里暂时不执行任何操作。
为什么需要它?
因为 Python 的类定义需要有一个代码块。我们现在还没有编写类的具体内容,所以可以先用 pass 占位。
你也可以把它理解为:先把类的框架搭起来,以后再逐步补充内容。
另外,注意 class Student: 最后有一个冒号 :,下面的 pass 则需要缩进。
Python 会通过缩进来区分哪些代码属于这个类。
现在,我们已经定义了一个名为 Student 的类。不过,这时候我们还没有创建任何学生对象。
接下来就要解决这个问题。
第一次创建对象
前面已经定义了 Student 类,现在我们尝试根据它创建一个对象。
请将下面的代码保存为 student.py:
1 | class Student: |
如果你使用 Windows,可以在终端中进入保存文件的目录,执行:
1 | python student.py |
如果你的环境需要使用 python3 命令,则执行:
1 | python3 student.py |
运行结果类似下面这样:
1 | <__main__.Student object at 0x00000204AA50D010> |
这里的内存地址只是示例。不同计算机、不同运行环境输出的地址可能不同,不需要与示例完全一致。
你可能会问:这串看起来很复杂的内容是什么?
先不用研究地址。我们只需要知道,Python 已经创建了一个 Student 对象,而 print(student1) 显示了这个对象的默认表示形式。
真正值得关注的是这行代码:
1 | student1 = Student() |
我们把它拆开理解。
Student() 做了什么
Student 是刚才定义的类。
在类名后面加上括号:
1 | Student() |
就可以调用这个类来创建一个新的对象。
你可以暂时把括号理解成一次调用操作。现在不需要掌握函数调用的所有细节,只要先记住:执行 Student(),就会创建一个新的 Student 对象。
student1 又是什么
再看完整的代码:
1 | student1 = Student() |
右侧的 Student() 创建了对象,左侧的 student1 则是一个变量名称,用来引用这个对象。
前面我们使用变量保存数字、字符串等数据,现在这个变量引用的是一个对象。
可以用下面的过程理解:
flowchart TD
A["定义 Student 类"] --> B["调用 Student()"]
B --> C["创建新的 Student 对象"]
C --> D["将对象引用赋值给 student1"]
D --> E["通过 student1 访问对象"]这里还需要区分两个步骤:
class Student:是定义类。Student()是调用类来创建对象。
定义类,并不意味着 Python 会自动创建任意数量的学生对象。需要几个对象,就可以根据实际需要调用几次类。
例如:
1 | class Student: |
这段代码创建了三个不同的 Student 对象。
虽然它们来自同一个类,但每次调用 Student() 都会创建一个新的对象。
不过,这三个对象目前还是空的。我们还没有给它们设置姓名和年龄。
怎样才能在创建对象时,把需要的数据一起交给它?
这就需要学习 __init__()。
为什么需要 init()
假设我们想创建一个学生对象,并且在创建时就提供姓名和年龄。
我们希望这样写:
1 | student1 = Student("Alice", 20) |
这行代码表达了一个很自然的需求:创建一个学生对象,并告诉它姓名是 Alice,年龄是 20。
但是,之前的类只有下面这些内容:
1 | class Student: |
它还没有定义如何接收姓名和年龄。
因此,我们需要在类中增加相应的初始化逻辑。
Python 为此提供了一种特殊方法,叫作 __init__()。
先看完整代码:
1 | class Student: |
运行结果:
1 | Alice |
这一次,程序成功输出了学生的姓名和年龄。
下面我们一步步弄清楚它是如何工作的。
init() 是什么
__init__() 是 Python 中的一个特殊方法,通常用于初始化对象。
什么叫初始化?
对于刚接触编程的人,可以先把初始化理解成:在对象刚创建出来时,为它准备好需要的数据。
例如,创建学生对象时,我们希望它一开始就有姓名和年龄,而不是创建之后再分别补充这些信息。
__init__() 就可以帮助我们完成这件事。
注意,__init__() 并不是负责创建对象本身。更准确地说,Python 会先创建对象,再在正常的类调用过程中自动执行 __init__() 来初始化对象。
对于目前的学习阶段,你可以先记住:
当我们执行下面的代码时:
1 | student1 = Student("Alice", 20) |
Python 会创建一个 Student 对象,并自动调用这个类定义的 __init__() 方法,让对象完成初始化。
通常不需要自己手动调用 __init__()。
name 和 age 从哪里来
回到这行代码:
1 | student1 = Student("Alice", 20) |
这里的 "Alice" 和 20 是我们在创建对象时提供的数据。
再看类中的方法:
1 | def __init__(self, name, age): |
其中,name 和 age 是用来接收这些数据的参数。
参数是什么?
你可以暂时把参数理解为:提前准备好的接收位置,用来接收调用时传入的数据。
在这次调用中:
| 传入的数据 | 接收它的参数 |
|---|---|
"Alice" | name |
20 | age |
因此,在执行初始化代码时,name 对应 "Alice",age 对应 20。
这还不是全部。接下来,我们要弄清楚,接收到的数据怎样才能保存在对象中。
理解 self 和 self.name = name
这是初学者学习类时最容易困惑的地方之一。
先看这两行代码:
1 | self.name = name |
特别是第一行:
1 | self.name = name |
等号两边都有 name,它们是不是同一个东西?
不是。
我们先从右侧开始理解。
等号右侧的 name
在下面的方法中:
1 | def __init__(self, name, age): |
name 是一个参数,用来接收创建对象时提供的姓名。
例如:
1 | student1 = Student("Alice", 20) |
这次调用中,name 接收到的值就是 "Alice"。
所以,下面代码中的右侧 name:
1 | self.name = name |
表示我们准备保存的姓名,也就是 "Alice"。
等号左侧的 self.name
那么,左侧的 self.name 又是什么意思?
先理解 self。
在类的方法中,self 是一个约定俗成的参数名称,用来引用当前正在操作的实例对象。
“实例对象”这个术语现在不用死记。你可以先简单理解为:当前正在初始化的那个具体对象。
例如,我们执行:
1 | student1 = Student("Alice", 20) |
Python 在初始化这个学生对象时,会让 self 引用当前正在初始化的对象。
因此:
1 | self.name |
表示当前这个对象中的 name 属性。
这里的“属性”可以先理解为对象保存的数据。后续我们还会专门学习属性的不同类型和管理方式。
现在再看:
1 | self.name = name |
整行代码的意思就是:
把右侧 name 中保存的姓名,存入当前对象的 name 属性中。
如果右侧的 name 是 "Alice",那么这行代码执行后,当前对象就保存了姓名 "Alice"。
同理:
1 | self.age = age |
表示把右侧 age 中的年龄,保存到当前对象的 age 属性中。
整个过程可以用下面的图来理解:
flowchart TD
A["调用 Student('Alice', 20)"] --> B["Python 创建对象"]
B --> C["自动调用 __init__()"]
C --> D["name 接收 Alice"]
C --> E["age 接收 20"]
D --> F["self.name = name"]
E --> G["self.age = age"]
F --> H["对象保存姓名 Alice"]
G --> I["对象保存年龄 20"]这里的 self.name 和 name 虽然名字相似,但用途不同:
name是接收数据的参数。self.name是当前对象中保存数据的属性。self.age与age的关系也是如此。
可以先把它记成一句话:右边拿到数据,左边把数据保存到对象中。
为什么不同对象可以保存不同的数据
理解了 self.name = name,我们再来看一个更重要的问题。
如果同一个类可以创建多个对象,那么这些对象能不能分别保存不同的数据?
当然可以。
请运行下面的完整代码:
1 | class Student: |
运行结果:
1 | Alice |
为什么会得到这样的结果?
我们按照执行顺序分析。
首先执行:
1 | student1 = Student("Alice", 20) |
Python 创建第一个学生对象,并初始化它的数据:
- 姓名保存为
Alice。 - 年龄保存为
20。
然后执行:
1 | student2 = Student("Bob", 22) |
Python 又创建了一个新的学生对象,并初始化它的数据:
- 姓名保存为
Bob。 - 年龄保存为
22。
虽然两次调用使用的是同一份 Student 类定义,但创建的是两个不同的对象。
每次初始化时,self 都引用当前正在初始化的对象。因此,第一次赋值会修改第一个对象的数据,第二次赋值则会修改第二个对象的数据。
可以把它们理解为两个独立的数据容器:
| 对象 | name 属性 | age 属性 |
|---|---|---|
student1 | Alice | 20 |
student2 | Bob | 22 |
当我们执行:
1 | print(student1.name) |
程序访问的是 student1 所引用对象中的姓名,所以输出 Alice。
而执行:
1 | print(student2.name) |
程序访问的是另一个对象中的姓名,所以输出 Bob。
这就是 self 非常重要的原因:它让同一份类定义能够在不同对象上工作,并且分别操作各自的数据。
还有一点需要记住:self 不是 Python 的关键字,而是 Python 社区普遍采用的命名惯例。我们编写类时,应该遵循这个惯例,使用 self 作为实例方法的第一个参数。
通过对象读取数据
到这里,我们已经知道如何在创建对象时保存姓名和年龄。
那么,保存之后,怎样读取这些数据呢?
其实前面的代码已经给出了答案:
1 | student1.name |
我们把完整示例再写一遍:
1 | class Student: |
运行结果:
1 | Alice |
这里的点号 . 是访问对象属性时常用的写法。
例如:
1 | student1.name |
表示访问 student1 所引用对象的 name 属性。
而:
1 | student1.age |
表示访问同一个对象的 age 属性。
我们可以把这个过程拆成三步:
student1:找到我们要操作的对象。.:表示接下来要访问这个对象中的某项内容。name:指定要访问的属性。
因此,student1.name 可以理解为:获取 student1 对象保存的姓名。
需要注意,student1 只是我们给变量起的名称,不是类的名称,也不是 self 的另一个写法。
当我们创建对象时,student1 引用第一个对象;在这个对象的方法运行期间,self 也会引用这个对象。
现在,我们已经初步掌握了创建对象、初始化对象和读取属性的基本过程。
综合示例:管理多个学生的信息
前面我们通过不同的小例子,逐步学习了类、对象、__init__() 和 self。
现在,把这些知识放在一起,编写一个完整的程序。
假设我们需要管理三名学生的信息,要求保存姓名和年龄,并能够输出每名学生的基本信息。
请将下面的代码保存为 student.py:
1 | class Student: |
运行程序:
1 | python student.py |
如果你的环境使用 python3 命令,也可以执行:
1 | python3 student.py |
运行结果:
1 | 学生姓名:Alice,年龄:20 |
这个例子中出现了一个之前没有详细介绍的方法:
1 | def show_info(self): |
先不用担心,我们只需要了解它的基本作用。
前面我们使用 __init__() 初始化对象。现在,我们通过 show_info() 定义一个用于显示学生信息的操作。
你可以先把它理解为:我们把“如何显示学生信息”这件事写进了 Student 类中,以后需要显示某个学生的信息时,就可以调用这个操作。
例如:
1 | student1.show_info() |
这里的意思是:让 student1 所引用的学生对象执行 show_info() 方法。
方法中的:
1 | self.name |
会分别访问当前对象保存的姓名和年龄。
因此,调用 student1.show_info() 时,显示的是 Alice 的信息;调用 student2.show_info() 时,显示的则是 Bob 的信息。
这也展示了类的另一个用途:除了组织数据,还可以把与这些数据相关的操作组织在一起。
再回顾一下整个程序的执行过程:
flowchart TD
A["定义 Student 类"] --> B["定义 __init__() 初始化方法"]
B --> C["定义 show_info() 显示方法"]
C --> D["创建 student1、student2、student3"]
D --> E["分别初始化每个对象的数据"]
E --> F["调用 student1.show_info()"]
E --> G["调用 student2.show_info()"]
E --> H["调用 student3.show_info()"]
F --> I["输出 Alice 的信息"]
G --> J["输出 Bob 的信息"]
H --> K["输出 Charlie 的信息"]注意,定义 show_info() 只是定义方法,并不会立即执行打印操作;只有调用 student1.show_info() 等语句时,对应方法才会运行。
动手练习:自己创建一个学生对象
学习编程不能只看示例,还要亲自修改代码。
下面给你一个小练习。
我们已经有了这样的类:
1 | class Student: |
请尝试完成以下任务:
- 创建一个名为
student4的对象,姓名为David,年龄为21。 - 调用这个对象的
show_info()方法。 - 再创建一个你自己选择姓名和年龄的学生对象。
例如,你可以先写出下面两行:
1 | student4 = Student("David", 21) |
运行后,应该看到:
1 | 学生姓名:David,年龄:21 |
如果运行成功,可以再试着修改姓名和年龄,看看输出结果是否随之变化。
这项练习看起来很简单,但它能帮助你确认自己是否真正理解了三个步骤:
- 使用类创建对象。
- 在创建对象时提供数据。
- 调用对象的方法。
如果还不熟练,可以回到前面的示例重新运行几遍。编程初学阶段,能够自己写出来,比一次性记住很多概念更重要。
写在最后
这篇文章从管理服务器和学生信息的实际问题出发,逐步介绍了 Python 中的类和对象。
对于初学者来说,现在不需要一下子掌握所有面向对象编程的知识。先理解下面这些基本概念就足够了。
类(Class)
类是组织相关数据和操作的一种方式。我们可以通过 class 关键字定义类。
对象(Object)
对象是根据类创建出来的具体实例。同一个类可以创建多个对象,每个对象都可以保存自己的数据。
__init__()
这是一个特殊方法,通常用于初始化对象。创建对象时,Python 会在正常的类调用过程中自动执行它。
self
它是实例方法中约定使用的第一个参数,用来引用当前正在操作的对象。
self.name = name
这行代码将右侧参数 name 的值保存到当前对象的 name 属性中。
对象属性和方法
可以通过 student1.name 这样的写法访问对象中的属性,也可以通过 student1.show_info() 这样的写法调用对象的方法。
最后,再记住一个核心思想:
类提供统一的定义,对象保存各自的数据,并可以执行类中定义的操作。
这就是我们学习 Python 面向对象编程的第一步。
下一篇,我们将继续学习 Python 的属性与装饰器,了解实例属性和类属性的区别,以及如何使用 @property 和 setter 更合理地管理对象中的数据。
