写 Python 时,真正陪伴你最多的并不是复杂语法,而是各种容器类型。列表、元组、字典、集合看起来都像“装数据的盒子”,但它们在顺序性、可变性、查找方式和适用场景上其实差别很大。
这一节我们会把这些常用容器分别拆开来看,讲清楚各自擅长解决什么问题,以及在真实业务中该如何选型。比起背 API,更重要的是建立一种“看见数据结构就能想到合适容器”的直觉。
列表:最常用的有序容器
如果只从日常使用频率来看,列表几乎可以算是 Python 里最常见的容器类型。它的核心特点可以先概括为三点:
- 有序;
- 可变;
- 可以存放任意类型的元素。
例如:
numbers = [1, 2, 3]
mixed = ["Python", 3.12, True]“有序”意味着你放进去的元素会保留顺序;“可变”意味着你可以在原有列表上继续增删改内容;而“可以存放任意类型”则说明它在表达上非常灵活。
也正因为太灵活,列表几乎成了很多人默认的第一选择。这个习惯并不一定错,但你要慢慢建立一个意识:列表适合“有顺序、可能变化、需要批量处理”的数据。
典型场景包括:
- 存放一组待处理任务;
- 维护一批有顺序的输入结果;
- 遍历接口返回的数据项;
- 在处理中不断追加新结果。
当你对“有顺序”和“可能修改”这两个特征都有需求时,列表通常就是非常自然的选择。
增删改查操作
列表最常见的操作,其实就是围绕增删改查展开:
items = ["a", "b", "c"]
items.append("d") # 追加
items.insert(1, "x") # 插入
items[0] = "z" # 修改
items.remove("b") # 按值删除
last = items.pop() # 按位置弹出在读取时,列表最大的特点就是支持基于索引访问:
print(items[0])
print(items[-1])这里顺手要建立两个直觉:
- 正索引从前往后;
- 负索引从后往前。
只要开始接触列表,索引几乎一定会反复出现,因此负索引这种写法很值得尽早熟悉。
不过,列表也不是没有代价。比如按索引查找某一位很方便,但如果你真正高频关心的是“某个键对应什么值”,或者“某个元素是否存在且需要高效判断”,那字典和集合往往会更适合。也就是说,列表好用,但不是万能容器。
切片与复制的区别
列表还有一个特别高频、也特别容易被误解的操作,就是切片:
nums = [1, 2, 3, 4, 5]
print(nums[1:4]) # [2, 3, 4]
print(nums[:3]) # [1, 2, 3]
print(nums[::2]) # [1, 3, 5]切片看起来像“取一段”,但它还有一个很容易被拿来做复制的用途:
copied = nums[:]这在很多入门资料里都会被写成“复制列表”的经典方式,但你需要知道,它更准确地说是浅复制,而不是彻底独立的深复制。
举个简单例子,如果列表里装的还是列表,那么切片复制并不会把内部嵌套结构也完全复制掉。这个问题我们在后面讲拷贝和常见陷阱时还会继续展开。当前阶段,你只需要先记住一点:切片复制适合简单列表,但遇到嵌套结构时要格外小心。
元组:不可变容器的价值
元组看起来和列表非常像,最大的不同在于:元组是不可变的。
point = (10, 20)你依然可以按索引访问它,但不能像列表那样随意修改、追加或删除元素。
刚开始接触时,很多人会疑惑:既然列表已经够用了,为什么还要有元组?
元组存在的价值,恰恰就在于它的不可变性。它更适合表达这些场景:
- 一组固定结构的数据;
- 不希望被中途修改的配置项;
- 函数返回的多值结果;
- 作为字典键时需要可哈希结构。
也就是说,列表更像“会变化的容器”,而元组更像“稳定的数据组合”。
比如坐标、RGB 颜色值、日期片段、数据库查询返回的一条固定结构记录,用元组来表达往往会更贴切。它不是为了代替列表,而是为了告诉读代码的人:这里的数据顺序是重要的,但结构不希望被随意改动。
另外,Python 里很多“返回多个值”的场景,本质上也是通过元组打包实现的:
def get_user():
return "Colin", 18
name, age = get_user()所以,元组虽然不如列表看起来那么“活跃”,但它在表达稳定结构方面非常有价值。
字典:键值映射的核心场景
如果说列表擅长“按顺序装一批元素”,那么字典擅长的就是“通过键找到值”。
user = {
"name": "Colin",
"age": 18,
"city": "Shanghai"
}字典的核心价值就在于映射关系。你不再关心“这个值排在第几个”,而是关心“这个键对应什么值”。
这也是为什么字典在业务代码中如此高频。只要你的数据带有字段名、属性名、配置项名、状态标识,字典几乎都会是第一候选。
典型场景包括:
- 表达一条记录;
- 存放配置项;
- 存放接口返回对象;
- 做字段名到字段值的映射。
列表和字典最大的思维差异在于:
- 列表强调位置;
- 字典强调语义化键名。
因此,只要你开始发现“我总是在用索引去记第 0 位是名字、第 1 位是年龄”,那往往就说明这个结构更适合用字典来表达。
字典遍历与常用方法
字典最常见的操作包括读取、写入、更新和遍历:
user = {"name": "Colin", "age": 18}
print(user["name"])
user["city"] = "Shanghai"
user["age"] = 19不过这里有一个很重要的习惯:如果你不确定某个键是否一定存在,优先考虑 get():
print(user.get("name"))
print(user.get("job"))相比直接使用 user["job"],get() 在键不存在时会更平滑一些,不会直接抛错。这在处理外部输入、接口返回和可选字段时尤其有用。
在遍历字典时,也有几种很常见的方式:
for key in user:
print(key)
for value in user.values():
print(value)
for key, value in user.items():
print(key, value)其中 items() 非常高频,因为它最适合同时拿到键和值。
所以,字典最值得建立的直觉是:只要你的数据有明确字段语义,而不是单纯按顺序排队,字典通常比列表更合适。
集合:去重、并集与交集
集合在初学阶段经常被低估,因为它不像列表和字典那样“看起来到处都在用”。但只要你的问题和“去重”有关,集合几乎立刻就会变得非常顺手。
Python 中的集合通常写作:
tags = {"python", "backend", "python"}
print(tags) # 自动去重它的核心特点主要有两个:
- 元素不重复;
- 非按索引顺序访问。
这意味着集合并不适合表达“有顺序的一组数据”,但非常适合表达“某些元素是否存在,以及集合之间如何做关系运算”。
最常见的场景包括:
- 数据去重;
- 成员判断;
- 求并集、交集、差集;
- 比较两批标签、权限或用户集合的关系。
例如:
a = {"python", "fastapi", "sqlalchemy"}
b = {"python", "django", "sqlalchemy"}
print(a | b) # 并集
print(a & b) # 交集
print(a - b) # 差集如果你在列表里做“是否存在”的高频判断,往往会越来越笨重;但如果问题本质上就是集合关系,那么直接用集合会自然很多。
所以,集合并不是“冷门容器”,而是专门为某类问题准备的高效表达方式。只不过它解决的问题比较集中,一旦命中场景,就会很好用。
到底该选哪一种容器?
学到这里,真正重要的问题其实不再是“每种容器怎么写”,而是“面对一个问题时,我该先想到谁”。
可以先用一个非常实用的判断方式来帮助自己快速选型:
1. 我是否关心顺序?如果关心,优先考虑列表或元组。
2. 这个结构会不会被修改?如果经常修改,偏向列表;如果结构固定,偏向元组。
3. 我更关心位置,还是关心字段名?关心位置,用列表 / 元组;关心字段语义,用字典。
4. 我最核心的问题是不是去重或成员关系?如果是,优先考虑集合。
你也可以把它们先粗略理解成这样:
- 列表:有序、可变、适合批量处理;
- 元组:有序、不可变、适合固定结构;
- 字典:键值映射、适合字段化数据;
- 集合:不重复、适合关系判断和去重。
真正的进步,不是把四种容器的方法都背下来,而是开始在读需求时自然地联想到合适的结构。一旦这种选型直觉建立起来,后面的循环、推导式、函数参数设计、数据建模都会更顺手。
总结与预告
这一节我们把 Python 中最常见的四类容器拆开讲清楚了,不只是会调用方法,更重要的是知道什么时候应该选哪一种结构。容器选型本质上是在为后续的逻辑复杂度和可维护性做准备。
接下来,我们会让这些数据真正流动起来,从条件分支、循环到推导式,开始进入程序控制与表达阶段。