2. 常见序列类型:字符串、列表、元组的对比与适用场景
序列类型,说白了就是一组有顺序的数据容器。Python 里最常见的三种——字符串、列表、元组——我几乎每天都在跟它们打交道。它们都能用下标访问,都能切片,都能迭代,但各自的脾气秉性完全不同。
我个人习惯把这三兄弟放在一起对比着学,因为只有搞清楚它们「哪里像、哪里不像」,写代码时才能选对工具。你想想看,选错了类型,轻则代码别扭,重则埋下性能或安全方面的坑。
2.1 三者的核心差异
先看一张对比表,把最关键的几个维度列清楚:
| 特性 | 字符串 (str) | 列表 (list) | 元组 (tuple) |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 不可变 |
| 元素类型 | 只能是字符 | 任意类型 | 任意类型 |
| 表示方式 | 'hello' 或 "hello" |
[1, 2, 3] |
(1, 2, 3) |
| 性能(访问/遍历) | 快 | 较快 | 最快 |
| 哈希能力 | 可哈希(可用作字典键) | 不可哈希 | 可哈希(元素均为可哈希时) |
| 典型用途 | 文本处理 | 动态数据集合 | 固定结构、函数返回值 |
嗯,这里有个关键点:可变性是区分它们最重要的标尺。列表可以增删改,字符串和元组一旦创建就不能变了。这个特性直接决定了你能对它们做什么操作,以及它们能用在哪些场景。
核心原则:需要频繁修改数据 → 选列表;数据一旦确定不再改变 → 选元组;处理文本 → 选字符串。别把元组当列表用,也别把列表塞进字典当键。
2.2 字符串:文本处理的基石
字符串没什么好说的,就是文本。但我在项目中见过太多人把它当列表用,比如用 str.replace() 循环替换字符,结果性能惨不忍睹。
为什么?因为字符串不可变。每次「修改」其实都创建了一个新字符串对象。你循环 1000 次,就创建了 1000 个临时对象。
# 错误示范:循环拼接字符串
s = ""
for i in range(10000):
s += str(i) # 每次创建新字符串,O(n²)
# 正确做法:用列表收集,最后 join
parts = []
for i in range(10000):
parts.append(str(i))
s = "".join(parts) # 一次拼接,O(n)
我曾经在一个日志处理脚本里踩过这个坑,处理 10 万行日志跑了 3 分钟。改成 join 之后,3 秒搞定。嗯,有时候性能问题就藏在这些细节里。
2.3 列表:最灵活的序列
列表是我用得最多的序列类型。它可变、可嵌套、可放任何东西。说白了就是一个「万能容器」。
但灵活也意味着责任。列表作为可变对象,有个容易踩的坑:作为函数默认参数。
def add_item(item, target_list=[]):
target_list.append(item)
return target_list
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 同一个列表对象!
print(add_item(3)) # [1, 2, 3]
我刚开始学 Python 时就被这个坑过。默认列表只创建一次,后续调用都在修改同一个对象。避坑指南:默认参数用 None,函数内部再创建新列表。
def add_item(item, target_list=None):
if target_list is None:
target_list = []
target_list.append(item)
return target_list
2.4 元组:不可变的「记录」
元组在 Python 里经常被低估。很多人觉得它就是「不能改的列表」,其实它的真正价值在于表达固定结构。
举个例子,函数返回多个值时,本质上返回的就是一个元组:
def get_user_info(user_id):
# 模拟查询
return ("张三", 28, "工程师") # 实际上返回的是元组
name, age, title = get_user_info(1001)
print(name, age, title) # 张三 28 工程师
我个人习惯用元组表示「记录」——比如坐标 (x, y)、RGB 颜色 (255, 128, 0)、数据库里的一行数据。因为这些数据天然就是固定字段、固定顺序,不该被修改。
小技巧:元组可以作为字典的键,列表不行。如果你需要把一组固定值作为键来查表,用元组。比如 cache = {("GET", "/api/users"): response_data}。
2.5 适用场景速查
下面这张表是我自己总结的,平时写代码时经常对照着看:
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 文本处理、正则匹配 | 字符串 | 丰富的字符串方法,不可变保证安全 |
| 动态数据收集(如用户输入) | 列表 | 需要频繁 append / pop / 修改 |
| 函数返回多个值 | 元组 | 解包方便,不可变防止误改 |
| 作为字典键或集合元素 | 元组 / 字符串 | 可哈希,列表不行 |
| 配置常量、枚举值 | 元组 | 不可变,语义清晰 |
| 需要排序、去重、随机访问 | 列表 | 内置 sort / set 转换 / 下标访问 |
| 大量数据只读遍历 | 元组 | 内存占用更小,访问更快 |
2.6 知识结构图
下面这张 SVG 图把三种序列类型的关系、特性和转换路径画清楚了:
2.7 避坑总结
最后,把我这些年踩过的坑汇总一下,你写代码时留个心眼:
- 别用
+或+=循环拼接字符串 —— 用列表收集 +join(),性能差距可能是数量级的。 - 别把可变对象(如列表)作为函数默认参数 —— 用
None加内部初始化。 - 别试图修改元组 —— 它不可变。真想改,转成列表再改,或者新建元组。
- 别把列表当字典键 —— 它不可哈希。用元组代替。
- 别在遍历列表时增删元素 —— 遍历副本或改用列表推导式。
我曾经在生产环境里遇到过一个诡异的 bug:一个函数用列表做默认参数,结果两次调用之间状态互相污染,查了整整一个下午。从那以后,我对可变默认参数有了心理阴影。嗯,希望你不用经历这种痛苦。
三种序列类型,各有各的脾气。选对了,代码干净利落;选错了,处处是坑。记住一句话:字符串管文本,列表管动态,元组管固定。大部分场景下,这个原则够用了。