返回专题首页

Python 专题

常见容器类型详解:列表、元组、字典与集合的选择

写 Python 时,真正陪伴你最多的并不是复杂语法,而是各种容器类型。列表、元组、字典、集合看起来都像“装数据的盒子”,但它们在顺序性、可变性、查找方式和适用场景上其实差别很大。

Python 专题第 05 篇 / 39 篇8 分钟

写 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 中最常见的四类容器拆开讲清楚了,不只是会调用方法,更重要的是知道什么时候应该选哪一种结构。容器选型本质上是在为后续的逻辑复杂度和可维护性做准备。

接下来,我们会让这些数据真正流动起来,从条件分支、循环到推导式,开始进入程序控制与表达阶段。