Python 基础篇(五):第五章:数据类型
Python 基础篇(五):第五章:数据类型
Prorise第五章. 数据容器:管理多个数据
在前面的章节中,我们学会了用变量存储单个数据,用运算符处理数据。但在实际开发中,我们经常需要处理大量数据。比如,一个班级 50 个学生的成绩、一个公司 100 个员工的信息、一个网站 1000 个用户的订单。
如果用单个变量存储这些数据,代码会变得非常冗长且难以维护。这一章,我们将学习 Python 的数据容器,它们可以帮助我们高效地管理多个数据。
5.1. 从单个变量到数据容器
5.1.1. 一个真实的困境:如何存储班级成绩?
假设你是一名老师,需要记录班级 5 个学生的考试成绩。你可能会这样写:
1 | score_1 = 90 |
看起来还可以接受。但现在,校长要求你计算这 5 个学生的平均分。你需要这样写:
1 | average = (score_1 + score_2 + score_3 + score_4 + score_5) / 5 |
如果班级有 50 个学生呢?你需要定义 50 个变量,然后在计算平均分时把它们全部加起来。这显然不现实。
更糟糕的是,如果学期中途转来一个新学生,你需要:
- 定义一个新变量
score_51 - 修改计算平均分的代码,把
score_51加进去 - 修改除数从 50 改为 51
这种做法有三个致命问题:
- 代码冗长:50 个变量,50 行代码
- 难以维护:每次增减学生,都要修改多处代码
- 无法批量操作:想找出最高分、最低分,需要写大量比较代码
列表可以解决这个问题:
1 | scores = [90, 85, 92, 88, 95] |
一个变量,存储所有成绩。计算平均分也变得简单:
1 | average = sum(scores) / len(scores) |
新增学生?只需要在列表末尾添加一个数字:
1 | scores.append(87) # 新学生的成绩 |
这就是数据容器的价值:用一个变量管理多个数据。
5.1.2. Python 的四种数据容器对比
Python 提供了四种常用的数据容器,它们各有特点:
| 容器类型 | 特点 | 适用场景 | 示例 |
|---|---|---|---|
列表 list | 有序、可变、可重复 | 存储一组可修改的数据 | [90, 85, 92] |
元组 tuple | 有序、不可变、可重复 | 存储不应被修改的数据 | (90, 85, 92) |
字典 dict | 无序、可变、键唯一 | 存储键值对数据 | {"name": "张三", "age": 25} |
集合 set | 无序、可变、不可重复 | 去重、集合运算 | {1, 2, 3} |
有序 vs 无序:
- 有序:元素有固定的位置,可以通过索引访问(如
scores[0]) - 无序:元素没有固定位置,不能通过索引访问
可变 vs 不可变:
- 可变:创建后可以修改、添加、删除元素
- 不可变:创建后不能修改,只能重新创建
可重复 vs 不可重复:
- 可重复:允许存储相同的元素(如
[1, 1, 2]) - 不可重复:自动去重(如
{1, 1, 2}会变成{1, 2})
5.1.3. 如何选择合适的容器?
场景 1:存储一组学生成绩
需求:可以修改成绩、添加新成绩、删除错误成绩。
选择:列表(可变、有序)
1 | scores = [90, 85, 92, 88, 95] |
场景 2:存储一个人的基本信息
需求:姓名、年龄、城市等信息,需要通过名称快速访问。
选择:字典(键值对)
1 | person = {"name": "张三", "age": 25, "city": "北京"} |
场景 3:存储地理坐标
需求:经度和纬度,创建后不应被修改。
选择:元组(不可变)
1 | location = (39.9042, 116.4074) # 北京的经纬度 |
场景 4:统计班级中有多少个不同的姓氏
需求:自动去重。
选择:集合(不可重复)
1 | surnames = {"张", "李", "王", "张", "李"} |
5.2. 列表(list):可变的有序容器
5.2.1. 为什么需要列表?
在 5.1.1 中,我们已经看到了列表的价值。现在,让我们深入理解列表的本质。
列表是什么?
列表是一个有序的容器,可以存储多个元素。这些元素可以是不同类型的数据:
1 | # 存储相同类型的数据 |
列表的特点:
- 有序:元素有固定的位置,第一个元素永远是第一个
- 可变:可以修改、添加、删除元素
- 可重复:允许存储相同的元素
5.2.2. 列表的创建与访问
创建列表
1 | # 方式 1:使用方括号 |
访问列表元素
列表的索引从 0 开始,第一个元素的索引是 0,第二个是 1,以此类推。
1 | scores = [90, 85, 92, 88, 95] |
负数索引
Python 支持负数索引,从列表末尾开始计数。-1 表示最后一个元素,-2 表示倒数第二个,以此类推。
1 | scores = [90, 85, 92, 88, 95] |
为什么索引从 0 开始?
这不仅仅是习惯,背后有技术原因。索引实际上表示的是"偏移量"(offset)。第一个元素距离列表起始位置的偏移是 0,第二个元素偏移 1 个位置,以此类推。这种设计让内存地址计算更高效。
虽然一开始可能不习惯,但理解了这个原理后,你会发现这是一个很自然的设计。
5.2.3. 列表的索引与切片
切片:获取列表的一部分
切片可以一次性获取列表的多个元素。语法是 list[start:end:step]:
start:起始索引(包含)end:结束索引(不包含)step:步长(可选,默认为 1)
1 | scores = [90, 85, 92, 88, 95, 78, 82] |
切片的关键规则:
- 结束索引不包含在结果中(
scores[0:3]只包含索引 0、1、2) - 如果起始索引大于等于结束索引,返回空列表
- 切片不会修改原列表,而是返回一个新列表
常见困惑:为什么结束索引不包含?
这是 Python 的设计哲学。这样设计有两个好处:
scores[:3]和scores[3:]可以完美分割列表,没有重叠scores[0:3]的长度就是3 - 0 = 3,计算更直观
5.2.4. 添加元素的三种方式
方式 1:append() - 在末尾添加一个元素
1 | scores = [90, 85, 92] |
append() 是最常用的添加方法,它会把元素添加到列表的末尾。
方式 2:insert() - 在指定位置插入元素
1 | scores = [90, 85, 92] |
insert() 需要两个参数:插入位置的索引和要插入的元素。原来在这个位置及之后的元素会向后移动。
方式 3:extend() - 批量添加元素
1 | scores = [90, 85, 92] |
extend() 会把另一个列表的所有元素逐一添加到当前列表的末尾。
append() vs extend():新手最容易混淆的点
1 | # append() 会把整个列表作为一个元素添加 |
如何记住?
append的意思是"追加",把整个东西追加到末尾extend的意思是"扩展",把列表扩展开来
5.2.5. 删除元素的四种方式
方式 1:remove() - 删除指定的元素
1 | scores = [90, 85, 92, 88, 85] |
remove() 会删除列表中第一个匹配的元素。如果元素不存在,会报错。
1 | scores = [90, 85, 92] |
方式 2:pop() - 删除指定索引的元素
1 | scores = [90, 85, 92, 88, 95] |
pop() 会删除指定索引的元素,并返回这个元素。如果不指定索引,默认删除最后一个元素。
1 | scores = [90, 85, 92, 88, 95] |
方式 3:clear() - 清空列表
1 | scores = [90, 85, 92, 88, 95] |
clear() 会删除列表中的所有元素,但列表本身还在。
方式 4:del - 删除元素或切片
1 | scores = [90, 85, 92, 88, 95] |
del 是 Python 的关键字,不是列表的方法。它可以删除任何变量。
四种删除方式的对比:
| 方式 | 删除依据 | 返回值 | 适用场景 |
|---|---|---|---|
remove(x) | 元素的值 | 无 | 知道要删除的值 |
pop(i) | 元素的索引 | 被删除的元素 | 知道要删除的位置,且需要这个值 |
clear() | 全部删除 | 无 | 清空列表 |
del | 索引或切片 | 无 | 删除多个元素或整个列表 |
5.2.6. 修改元素与查找元素
修改元素
直接通过索引赋值即可修改元素:
1 | scores = [90, 85, 92, 88, 95] |
也可以通过切片修改多个元素:
1 | scores = [90, 85, 92, 88, 95] |
查找元素:index() 方法
index() 方法返回元素第一次出现的索引:
1 | scores = [90, 85, 92, 88, 85] |
如果元素不存在,会报错:
1 | scores = [90, 85, 92, 88, 95] |
查找元素:in 运算符
in 运算符可以判断元素是否存在于列表中,返回 True 或 False:
1 | scores = [90, 85, 92, 88, 95] |
统计元素出现次数:count() 方法
1 | scores = [90, 85, 92, 88, 85, 85] |
获取列表长度:len() 函数
1 | scores = [90, 85, 92, 88, 95] |
5.2.7. 列表的排序:sort vs sorted
sort() - 原地排序
sort() 方法会直接修改原列表,按升序排列:
1 | scores = [90, 85, 92, 88, 95] |
降序排列需要设置 reverse=True:
1 | scores = [90, 85, 92, 88, 95] |
sorted() - 返回新列表
sorted() 函数不会修改原列表,而是返回一个新的排序后的列表:
1 | scores = [90, 85, 92, 88, 95] |
同样可以降序排列:
1 | sorted_scores = sorted(scores, reverse=True) |
sort() vs sorted():何时用哪个?
| 特性 | sort() | sorted() |
|---|---|---|
| 是否修改原列表 | 是 | 否 |
| 返回值 | None | 新列表 |
| 适用场景 | 不需要保留原列表 | 需要保留原列表 |
常见错误:误以为 sort() 有返回值
1 | scores = [90, 85, 92, 88, 95] |
如何避免? 记住:sort() 是"就地排序",它修改原列表,不返回新列表。
5.2.8. 列表的常见错误与避坑指南
错误 1:索引越界
1 | scores = [90, 85, 92] |
为什么会犯这个错误? 因为我们习惯性地认为"长度为 3"意味着"可以访问到第 3 个"。但由于索引从 0 开始,长度为 3 的列表有效索引是 0、1、2。
如何避免? 记住这个规则:最大索引 = 列表长度 - 1
错误 2:混淆 append 和 extend
1 | scores = [90, 85, 92] |
如何避免?
- 添加单个元素用
append() - 添加多个元素用
extend()
错误 3:切片的结束索引不包含
1 | scores = [90, 85, 92, 88, 95] |
为什么这样设计? 这样可以让 scores[:3] 和 scores[3:] 完美分割列表,没有重叠。
错误 4:误以为 sort() 有返回值
1 | scores = [90, 85, 92] |
如何避免?
- 需要修改原列表用
sort() - 需要新列表用
sorted()
5.2.9. 实战:成绩管理器(不用循环的版本)
现在,我们用学到的知识做一个简单的成绩管理器。
需求:
- 存储 5 个学生的成绩
- 添加新成绩
- 删除错误成绩
- 修改成绩
- 查找最高分和最低分
- 计算平均分
- 排序成绩
1 | # 初始化成绩列表 |
输出结果:
1 | 初始成绩:[90, 85, 92, 88, 95] |
5.3. 元组(tuple):不可变的列表
5.3.1. 元组 vs 列表:何时用元组?
在学习元组之前,你可能会有一个疑问:既然列表已经很好用了,为什么还需要元组?
元组的核心特点:不可变
元组和列表最大的区别是:元组创建后不能修改。你不能添加、删除、修改元组中的元素。
1 | # 列表:可以修改 |
为什么需要不可变的容器?
在某些场景下,我们希望数据创建后不被修改。比如:
场景 1:地理坐标
地理坐标(经度、纬度)一旦确定,就不应该被修改。如果用列表存储,可能会被意外修改:
1 | # 用列表存储(不推荐) |
场景 2:函数返回多个值
当函数需要返回多个值时,通常使用元组:
1 | def get_user_info(): |
场景 3:作为字典的键
字典的键必须是不可变的。列表不能作为字典的键,但元组可以:
1 | # 用元组作为字典的键 |
元组 vs 列表的选择原则:
- 数据需要修改 → 用列表
- 数据不应被修改 → 用元组
- 不确定 → 用列表(更灵活)
5.3.2. 元组的创建:单元素陷阱
创建元组
1 | # 方式 1:使用小括号 |
单元素元组的陷阱
这是新手最容易犯的错误:
1 | # ❌ 错误:这不是元组,是整数 |
为什么单元素元组必须加逗号?
因为小括号在 Python 中有多种用途:
- 数学运算:
(1 + 2) * 3 - 函数调用:
print(42) - 元组:
(1, 2, 3)
如果单元素元组不加逗号,Python 无法区分它是元组还是数学运算。加上逗号后,Python 就知道这是一个元组。
5.3.3. 元组的访问与切片
元组的访问和切片与列表完全相同:
1 | scores = (90, 85, 92, 88, 95) |
元组的方法
由于元组不可变,它只有两个方法:
1 | scores = (90, 85, 92, 88, 85) |
元组也支持常用的内置函数:
1 | scores = (90, 85, 92, 88, 95) |
5.3.4. 元组解包:优雅的多重赋值
元组解包是 Python 的一个优雅特性,它可以让你一次性给多个变量赋值。
基础解包
1 | # 创建元组 |
实际上,我们在第三章学过的多重赋值,本质上就是元组解包:
1 | # 这两种写法是等价的 |
交换变量的值
元组解包让交换变量变得非常简单:
1 | a, b = 10, 20 |
在其他编程语言中,交换变量通常需要一个临时变量:
1 | # 传统方法(不推荐) |
Python 的元组解包让这个过程变得优雅而直观。
忽略不需要的值
有时候,我们只需要元组中的部分值,可以用下划线 _ 表示忽略:
1 | person = ("张三", 25, "北京", "男") |
下划线 _ 是 Python 的惯例,表示"这个变量我不关心"。
5.3.5. 元组的不可变性:真的不能改吗?
元组本身不可变
元组创建后,不能添加、删除、修改元素:
1 | scores = (90, 85, 92) |
但可以创建新元组
虽然不能修改元组,但可以通过连接、重复等操作创建新元组:
1 | scores = (90, 85, 92) |
元组中的可变对象可以修改
这是一个容易混淆的点。元组的不可变性指的是元组的结构不可变(元素的引用不能改变),但如果元组中包含可变对象(如列表),这些对象的内容是可以修改的:
1 | # 元组中包含列表 |
如何理解?
想象元组是一个相框,里面有三个格子,每个格子放一张照片。元组的不可变性是指:
- 不能增加或减少格子
- 不能把某个格子的照片换成另一张
但如果某个格子里放的是一个相册(列表),你可以在相册里添加或删除照片,因为相册本身是可变的。
5.3.6. 元组的常见错误与避坑指南
错误 1:单元素元组忘记加逗号
1 | # ❌ 错误 |
错误 2:尝试修改元组
1 | scores = (90, 85, 92) |
如何避免? 如果需要修改数据,一开始就用列表,不要用元组。
错误 3:混淆元组和列表的语法
1 | # 列表用方括号 |
5.4. 字典(dict):键值对的世界
5.4.1. 为什么需要字典?
假设你要存储一个学生的信息:姓名、年龄、城市、成绩。用列表可以这样写:
1 | student = ["张三", 25, "北京", 90] |
但这样有一个问题:你需要记住每个位置代表什么。student[0] 是姓名,student[1] 是年龄,student[2] 是城市……如果信息很多,很容易搞混。
字典可以解决这个问题:
1 | student = { |
字典使用"键值对"的方式存储数据。每个数据都有一个名字(键),通过名字可以快速找到对应的值。
字典的特点:
- 无序:元素没有固定的位置(Python 3.7+ 保持插入顺序,但不应依赖这个特性)
- 可变:可以添加、删除、修改键值对
- 键唯一:每个键只能出现一次,重复的键会被覆盖
5.4.2. 字典的创建与访问
创建字典
1 | # 方式 1:使用花括号 |
访问字典的值
1 | student = {"name": "张三", "age": 25, "city": "北京"} |
如果键不存在会报错:
1 | student = {"name": "张三", "age": 25} |
5.4.3. 字典的增删改查
添加或修改键值对
1 | student = {"name": "张三", "age": 25} |
字典的赋值操作很智能:如果键不存在,就添加;如果键已存在,就修改。
删除键值对
1 | student = {"name": "张三", "age": 25, "city": "北京"} |
查询键是否存在
1 | student = {"name": "张三", "age": 25, "city": "北京"} |
5.4.4. get() vs 直接访问:避免 KeyError
当访问不存在的键时,直接访问会报错:
1 | student = {"name": "张三", "age": 25} |
get() 方法可以避免这个问题:
1 | student = {"name": "张三", "age": 25} |
get() vs 直接访问的对比:
| 方式 | 键存在 | 键不存在 | 适用场景 |
|---|---|---|---|
dict[key] | 返回值 | 报错 KeyError | 确定键存在 |
dict.get(key) | 返回值 | 返回 None | 不确定键是否存在 |
dict.get(key, default) | 返回值 | 返回默认值 | 需要默认值 |
字典的其他常用方法:
1 | student = {"name": "张三", "age": 25, "city": "北京"} |
5.4.5. 字典的常见错误与避坑指南
错误 1:访问不存在的键
1 | student = {"name": "张三", "age": 25} |
如何避免? 使用 get() 方法或先用 in 判断键是否存在。
错误 2:键必须是不可变类型
1 | # ❌ 错误:列表不能作为键 |
为什么? 字典的键必须是不可变的(可哈希的)。列表是可变的,不能作为键。
错误 3:重复的键会被覆盖
1 | student = {"name": "张三", "age": 25, "age": 26} |
5.4.6. 实战:通讯录管理器
现在,我们用字典做一个简单的通讯录管理器。
1 | # 创建通讯录(字典的字典) |
输出结果:
1 | ================================================== |
5.5. 集合(set):去重与集合运算
5.5.1. 为什么需要集合?
假设你要统计一个班级中有多少个不同的姓氏。用列表存储所有学生的姓氏:
1 | surnames = ["张", "李", "王", "张", "李", "赵", "张", "王"] |
如果用列表,你需要手动去重。但集合可以自动去重:
1 | surnames_set = {"张", "李", "王", "张", "李", "赵", "张", "王"} |
集合的特点:
- 无序:元素没有固定的位置,不能通过索引访问
- 可变:可以添加、删除元素
- 不可重复:自动去重,相同的元素只保留一个
5.5.2. 集合的创建与特性
创建集合
1 | # 方式 1:使用花括号 |
集合的自动去重
1 | # 列表中有重复元素 |
集合是无序的
1 | numbers = {5, 2, 8, 1, 9} |
由于集合是无序的,不能通过索引访问元素:
1 | numbers = {1, 2, 3} |
5.5.3. 集合的增删操作
添加元素
1 | numbers = {1, 2, 3} |
删除元素
1 | numbers = {1, 2, 3, 4, 5} |
5.5.4. 集合运算:交并差补
集合支持数学中的集合运算,这在数据分析中非常有用。
交集:两个集合的共同元素
1 | set1 = {1, 2, 3, 4, 5} |
并集:两个集合的所有元素(去重)
1 | set1 = {1, 2, 3, 4, 5} |
差集:属于 set1 但不属于 set2 的元素
1 | set1 = {1, 2, 3, 4, 5} |
对称差集:不同时属于两个集合的元素
1 | set1 = {1, 2, 3, 4, 5} |
5.5.5. 集合的常见错误与避坑指南
错误 1:创建空集合用 {}
1 | # ❌ 错误:这是空字典,不是空集合 |
错误 2:尝试通过索引访问集合
1 | numbers = {1, 2, 3} |
如何避免? 记住:集合是无序的,不支持索引访问。
错误 3:集合中的元素必须是不可变类型
1 | # ❌ 错误:列表不能作为集合的元素 |
5.5.6. 实战:数据去重与关系分析
场景 1:去除列表中的重复元素
1 | # 原始数据(有重复) |
场景 2:分析两个班级的学生关系
1 | # 班级 A 的学生 |
输出结果:
1 | 同时在两个班级的学生:{'王五', '赵六'} |
5.6. 字符串的进阶操作
5.6.1. 字符串也是容器
在第三章中,我们学习了字符串的基础用法。现在,我们要深入理解:字符串也是一种容器。
字符串是一个有序的、不可变的字符序列。你可以把它看作一个"只读的字符列表"。
1 | text = "Python" |
5.6.2. 字符串的切片与索引
字符串的索引和切片与列表完全相同:
1 | text = "Hello, Python!" |
字符串是不可变的
虽然可以访问字符串的字符,但不能修改:
1 | text = "Hello" |
如果需要修改,只能创建新字符串:
1 | text = "Hello" |
5.6.3. 字符串的常用方法(分类讲解)
字符串有很多方法,我们按功能分类讲解。
判断类方法(返回 True/False)
1 | text = "Hello123" |
查找类方法
1 | text = "Hello, Python! Python is great!" |
转换类方法
1 | text = "Hello, Python!" |
去除空格类方法
1 | text = " Hello, Python! " |
5.6.4. 字符串的分割与拼接
split():分割字符串为列表
1 | # 按空格分割 |
join():拼接列表为字符串
1 | # 用空格拼接 |
split() 和 join() 的配合使用
1 | # 场景:将日期格式从 "2026-02-07" 转换为 "2026/02/07" |
5.6.5. 字符串的常见错误与避坑指南
错误 1:尝试修改字符串
1 | text = "Hello" |
如何避免? 记住:字符串是不可变的。如果需要修改,创建新字符串。
错误 2:混淆 split() 和 join() 的使用对象
1 | # ❌ 错误:split() 是字符串的方法 |
如何记住?
split():字符串 → 列表("分割"字符串)join():列表 → 字符串("拼接"列表)
错误 3:strip() 只去除两端的字符
1 | text = " Hello Python " |
如果要去除所有空格,需要用 replace():
1 | text = " Hello Python " |
5.7. 数据容器的类型转换
5.7.1. 列表、元组、集合的相互转换
列表 → 元组
1 | scores_list = [90, 85, 92, 88, 95] |
元组 → 列表
1 | scores_tuple = (90, 85, 92, 88, 95) |
列表 → 集合(自动去重)
1 | scores_list = [90, 85, 92, 90, 88, 85] |
集合 → 列表
1 | scores_set = {90, 85, 92, 88, 95} |
元组 → 集合
1 | scores_tuple = (90, 85, 92, 90, 88) |
集合 → 元组
1 | scores_set = {90, 85, 92, 88, 95} |
5.7.2. 字符串与列表的转换
字符串 → 列表(split)
1 | # 按空格分割 |
列表 → 字符串(join)
1 | words = ["Hello", "Python", "Java"] |
5.7.3. 类型转换的常见陷阱
陷阱 1:字典转换为列表
1 | student = {"name": "张三", "age": 25, "city": "北京"} |
陷阱 2:集合转换后顺序不确定
1 | numbers = [5, 2, 8, 1, 9, 2, 5] |
如果需要保持顺序,可以先排序:
1 | numbers = [5, 2, 8, 1, 9, 2, 5] |
陷阱 3:join() 只能拼接字符串列表
1 | numbers = [1, 2, 3, 4, 5] |
注意:这里用到了生成器表达式 str(n) for n in numbers,它会在下一章详细讲解。现在你只需要知道,它可以把列表中的每个元素转换为字符串。
5.8. 本章小结
本章学习了 Python 的四种数据容器:列表、元组、字典、集合,以及字符串的进阶操作。
核心要点:
| 容器类型 | 特点 | 使用场景 | 关键操作 |
|---|---|---|---|
列表 list | 有序、可变、可重复 | 存储可修改的数据 | append(), pop(), sort() |
元组 tuple | 有序、不可变、可重复 | 存储不应修改的数据 | 元组解包、作为字典的键 |
字典 dict | 无序、可变、键唯一 | 存储键值对数据 | get(), keys(), items() |
集合 set | 无序、可变、不可重复 | 去重、集合运算 | &, ` |
容器选择原则:
- 需要按顺序存储多个值 → 列表
- 数据不应被修改 → 元组
- 需要通过名称访问数据 → 字典
- 需要去重或集合运算 → 集合
常见操作对比:
| 操作 | 列表 | 元组 | 字典 | 集合 |
|---|---|---|---|---|
| 创建 | [1, 2, 3] | (1, 2, 3) | {"a": 1} | {1, 2, 3} |
| 访问 | list[0] | tuple[0] | dict["key"] | 不支持索引 |
| 修改 | ✅ | ❌ | ✅ | ✅ |
| 添加 | append() | ❌ | dict[key] = value | add() |
| 删除 | remove(), pop() | ❌ | del, pop() | remove(), discard() |
| 长度 | len() | len() | len() | len() |
下一章,我们将学习条件判断(if/elif/else)和用户输入(input),让程序能够根据不同情况做出不同反应。




