1. 序列模式基础:什么是序列模式?
序列模式,说白了就是「有顺序的数据集合」。你想想看,我们平时写代码,最常打交道的就是列表、元组、字符串这些东西。它们都有一个共同特点——元素按固定顺序排列。
我个人习惯把序列模式理解为「排队买奶茶」。每个人站在固定的位置上,你不能随便插队,想找谁就按位置编号去找。这个编号,就是索引。
核心定义:序列模式是一种数据结构组织方式,其中元素按照确定的顺序排列,每个元素拥有唯一的位置编号(索引),并且支持从头到尾的遍历操作。
1.1 序列模式的三大核心特征
我在项目中遇到过不少新手把序列和集合搞混。其实区分起来很简单,记住三个特征就够了:
| 特征 | 说明 | 举个栗子 |
|---|---|---|
| 有序性 | 元素有固定的先后顺序,顺序本身携带信息 | "abc" 和 "cba" 是不同的序列 |
| 索引访问 | 通过整数下标直接定位元素,时间复杂度 O(1) | my_list[2] 拿到第三个元素 |
| 可迭代 | 可以用循环逐个取出元素,支持 for...in 语法 | for item in sequence: ... |
有序性——顺序就是信息
这一点我吃过亏。曾经有个项目,我用字典存了一组配置项,结果在不同 Python 版本下遍历顺序不一致,导致配置解析出错。后来改成列表,问题就解决了。
为什么会这样?因为字典在 3.7 之前不保证顺序,而序列永远保证。你放进去的顺序,就是它出来的顺序。
小技巧:当你需要「第一个是什么、第二个是什么」这种语义时,别犹豫,用序列。字典适合「根据名字找值」,序列适合「根据位置找值」。
索引访问——O(1) 的快乐
索引从 0 开始,这是 C 语言留下的传统。Python 还支持负数索引,-1 表示最后一个元素。嗯,这里要注意:索引越界会抛 IndexError,写代码时记得检查边界。
# 索引访问示例
fruits = ["苹果", "香蕉", "橘子", "葡萄"]
print(fruits[0]) # 苹果
print(fruits[-1]) # 葡萄
print(fruits[1:3]) # ['香蕉', '橘子'] —— 切片操作
可迭代——遍历的优雅
所有序列都是可迭代的。这意味着你可以用 for 循环、列表推导式、map/filter 等函数式工具来处理它们。我曾经在数据处理脚本里,用一行列表推导式替代了 8 行 for 循环,代码瞬间清爽不少。
# 可迭代的威力
numbers = [1, 2, 3, 4, 5]
# 普通写法
squares = []
for n in numbers:
squares.append(n * n)
# 序列迭代写法
squares = [n * n for n in numbers] # [1, 4, 9, 16, 25]
1.2 序列模式的知识体系
下面这张图是我自己梳理的序列模式知识结构,你可以把它当作本章的地图:
1.3 序列 vs 非序列:一张表看懂
很多同学问我:「什么时候用序列,什么时候用集合或字典?」我的回答是:看你要不要「顺序」这个属性。
| 对比维度 | 序列模式 | 非序列模式(集合/字典) |
|---|---|---|
| 顺序保证 | ✅ 严格有序 | ❌ 无序(或插入顺序,不保证位置语义) |
| 访问方式 | 索引(整数下标) | 键(key)或哈希值 |
| 重复元素 | ✅ 允许重复 | ❌ 集合不允许,字典 key 不允许 |
| 典型场景 | 排行榜、队列、文本处理 | 去重、缓存、映射关系 |
| 时间复杂度 | 索引 O(1),查找 O(n) | 查找 O(1) 平均 |
避坑指南:我曾经在日志分析系统里用列表存了 10 万条记录,然后频繁用 if item in my_list 做查找。结果性能惨不忍睹。后来换成集合,速度提升了上百倍。记住:查找频繁用集合,顺序重要用列表。
1.4 序列模式的通用操作
不管哪种序列类型,都支持下面这些操作。我建议你把这些记牢,写代码时能省不少事:
# 序列通用操作一览
seq = [1, 2, 3, 4, 5]
# 1. 索引访问
seq[0] # 1
seq[-1] # 5
# 2. 切片
seq[1:4] # [2, 3, 4]
seq[::2] # [1, 3, 5]
# 3. 拼接与重复
seq + [6, 7] # [1, 2, 3, 4, 5, 6, 7]
seq * 3 # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
# 4. 成员检测
3 in seq # True
10 not in seq # True
# 5. 长度与最值
len(seq) # 5
max(seq) # 5
min(seq) # 1
# 6. 遍历
for i, val in enumerate(seq):
print(f"位置 {i} 的值是 {val}")
个人经验:enumerate() 是我最常用的函数之一。它同时给你索引和值,比 range(len(seq)) 优雅得多。我写代码时几乎不用 range(len(...)) 这种写法了。
1.5 序列模式的边界与局限
序列模式不是万能的。我遇到过几个典型问题,分享给你:
- 插入/删除效率低:在列表头部插入元素是 O(n) 操作。如果你频繁在两端操作,考虑用
collections.deque。 - 查找效率低:线性查找 O(n)。数据量大时,考虑用集合或二分查找(前提是已排序)。
- 内存占用:序列存储所有元素,不像生成器那样惰性求值。处理超大数据集时注意内存。
嗯,到这里你应该对序列模式有了整体认识。记住三个关键词:有序、索引、可迭代。后面我们会深入每种序列类型的具体用法和坑点。