Python数据类型-基础数据类型-序列类型 --- list, tuple, range
一、通用序列操作
- 大多数序列类型,包括可变类型和不可变类型都支持下表中的操作。collections.abc.Sequence ABC被提供用来更容易地在自定义序列类型上正确地实现这些操作。
- 此表按优先级升序列出了序列操作。在表格中,s 和 t 是具有相同类型的序列,n, i, j 和 k 是整数而 x 是任何满足 s 所规定的类型和值限制的任意对象。
- in 和 not in 操作具有与比较操作相同的优先级。+ (拼接) 和 * (重复) 操作具有与对应数值运算相同的优先级。
| 运算 | 结果 | 备注 |
|---|---|---|
| x in s | 若 s 中某一项等于 x 则为 True,否则为 False | (1) |
| x not in s | 若 s 中某一项等于 x 则为 False,否则为 True | (1) |
| s + t | 将序列 s 与 t 拼接得到新序列 | (6)(7) |
| s * n 或 n * s | 将序列 s 重复 n 次后拼接成新序列 | (2)(7) |
| s[i] | 返回序列 s 的第 i 项,下标从 0 开始 | (3) |
| s[i:j ] | 返回序列 s 从索引 i 到 j(不含 j)的切片 | (3)(4) |
| s[i:j:k ] | 返回序列 s 从索引 i 到 j、步长为 k 的切片 | (3)(5) |
| len(s) | 返回序列 s 的元素个数 | — |
| min(s) | 返回序列 s 中的最小项 | — |
| max(s) | 返回序列 s 中的最大项 | — |
| s.index(x[, i[, j]]) | 返回 x 在 s 中首次出现的位置索引;可限定搜索范围 [i, j) | (8) |
| s.count(x) | 返回 x 在序列 s 中出现的总次数 | — |
1、index(self, value, start=0, stop=sys.maxsize) → int
1.1、在序列的指定切片 [start:stop ) 范围内,从左到右查找第一次出现的元素 value,并返回其下标。若未找到,抛出 ValueError。
1.2、参数
- value(必选):要查找的元素,与序列元素做 == 比较。
- start(可选,默认为 0):切片起始下标(包含)。
- stop(可选,默认为 sys.maxsize):切片结束下标(不包含)。
1.3、返回值
- 找到时返回 int 类型的下标(相对于原序列,而非切片)。
- 未找到时抛出 ValueError:
is not in sequence。
1.4、边界/细节
- 支持负索引;start 或 stop 越界会被自动截断。
- 若序列中允许重复元素,只返回 最左侧 的第一次出现。
- 时间复杂度平均为 O(n),因为需要线性扫描。
>>> from collections.abc import Sequence>>> t = ('a', 'b', 'c', 'b')>>> Sequence.index(t, 'b') # 等价于 t.index('b')1>>> t.index('b', 2) # 从下标 2 开始找3>>> t.index('x')Traceback (most recent call last): ...ValueError: 'x' is not in tuple2、count(self, value) → int
2.1、统计序列中 所有位置(无切片参数)与 value 相等的元素出现次数。
2.2、参数
- value(必选):要计数的元素,使用 == 比较。
2.3、返回值
- int:出现次数;若一次也没有返回 0。
- 不会抛出异常,即使元素不存在。
2.4、边界/细节
- 同样使用线性扫描,时间复杂度 O(n)。
- 对于可变序列(如 list),统计的是调用时刻的快照。
- 字符串、元组、列表、bytes 等内部实现均遵循同一语义。
>>> seq = [1, 2, 1, 3, 1]>>> seq.count(1)3>>> seq.count(99)0>>> 'banana'.count('na')23、len()获取列表长度
# 获取列表长度print(len([1, 2, 3, 4]))
# 输出结果44、max()获取列表值最大的元素
# maxa = [1, 2, 3]print(max(a))
# 输出结果35、min()获取列表值最小的元素
二、不可变序列类型
- 不可变序列类型普遍实现而可变序列类型未实现的唯一操作就是对hash() 内置函数的支持。
- 这种支持允许不可变类型,例如tuple 实例被用作dict 键,以及存储在set 和frozenset 实例中。
- 尝试对包含有不可哈希值的不可变序列进行哈希运算将会导致TypeError。
三、可变序列类型
- 以下表格中的操作是在可变序列类型上定义的。collections.abc.MutableSequence ABC 被提供用来更容易地在自定义序列类型上正确实现这些操作。
- 表格中的 s 是可变序列类型的实例,t 是任意可迭代对象,而 x 是符合对 s 所规定类型与值限制的任何对象 (例如,bytearray 仅接受满足 0 <= x <= 255 值限制的整数)。
| 运算 | 结果 | 备注 |
|---|---|---|
| s[i] = x | 将 s 的第 i 项替换为 x | — |
| s[i:j ] = t | 将 s 从 i 到 j 的切片替换为可迭代对象 t 的内容 | — |
| del s[i:j ] | 等同于 s[i:j ] = [] | — |
| s[i:j:k ] = t | 将 s[i:j:k ] 的元素替换为 t 的元素 | (1) |
| del s[i:j:k ] | 从列表中移除 s[i:j:k ] 的元素 | — |
| s.append(x) | 将 x 添加到序列的末尾 | 等同于 s[len(s):len (s)] = [x] |
| s.clear() | 从 s 中移除所有项 | 等同于 del s[:] (5) |
| s.copy() | 创建 s 的浅拷贝 | 等同于 s[:] (5) |
| s.extend(t) 或 s += t | 用 t 的内容扩展 s | 等同于 s[len(s):len (s)] = t |
| s *= n | 使用 s 的内容重复 n 次来对其进行更新 | (6) |
| s.insert(i, x) | 在索引 i 处插入 x | 等同于 s[i:i ] = [x] |
| s.pop() 或 s.pop(i) | 移除并返回索引 i 处的元素 | (2) |
| s.remove(x) | 移除第一个等于 x 的元素 | (3) |
| s.reverse() | 就地逆序列表元素 | (4) |
1、append(self, value) → None
- 作用:在序列尾部追加单个元素。
- 参数:value – 待追加的对象。
- 返回值:None(就地修改)。
- 复杂度:均摊 O(1)。
>>> lst = [1, 2]>>> lst.append(3)>>> lst[1, 2, 3]2、clear(self) → None
- 作用:删除序列中所有元素,使其长度变为 0。
- 参数:无。
- 返回值:None。
>>> lst = [1, 2, 3]>>> lst.clear()>>> lst[]3、copy(self) → MutableSequence
- 作用:返回序列的浅拷贝(新对象,元素本身不复制)。
- 参数:无。
- 返回值:与原序列同类型的空壳副本。
>>> lst = [[1], [2]]>>> cp = lst.copy()>>> cp[0].append(9) # 修改元素,原列表受影响>>> lst[[1, 9], [2]]4、extend(self, iterable) → None
- 作用:将可迭代对象中的所有元素依次追加到尾部。
- 参数:iterable – 任意可迭代对象(列表、元组、生成器等)。
- 返回值:None。
- 复杂度:O(k),k 为 iterable 长度。
>>> lst = [1, 2]>>> lst.extend((3, 4))>>> lst[1, 2, 3, 4]5、insert(self, index, value) → None
- 作用:在指定下标前插入元素;负索引也可。
- 参数:index – 插入位置;value – 待插入元素。
- 返回值:None。
- 复杂度:O(n)。
>>> lst = ['a', 'c']>>> lst.insert(1, 'b')>>> lst['a', 'b', 'c']6、pop(self, index=-1) → element
- 作用:移除并返回指定下标的元素;默认移除最后一个。
- 参数:index – 可选,默认为 -1。
- 返回值:被弹出的元素。
- 异常:IndexError(空序列或越界)。
>>> lst = [10, 20, 30]>>> lst.pop()30>>> lst.pop(0)10>>> lst[20]7、remove(self, value) → None
- 作用:删除第一个与 value 相等的元素。
- 参数:value – 待移除的值(按 == 比较)。
- 返回值:None。
- 异常:ValueError(元素不存在)。
>>> lst = [1, 2, 3, 2]>>> lst.remove(2)>>> lst[1, 3, 2]8、reverse(self) → None
- 作用:就地反转序列顺序。
- 参数:无。
- 返回值:None。
- 复杂度:O(n)。
>>> lst = [1, 2, 3]>>> lst.reverse()>>> lst[3, 2, 1]四、列表 list
- 列表是可变序列,通常用于存放同类项目的集合(其中精确的相似程度将根据应用而变化)
1、构造方法:
1.1、字面量构造(空列表或初值列表)
q = [] # 空队列q = [1, 2, 3] # 带初始元素- 最轻量,O(1) 创建;
- 只负责“容器”,不提供线程安全或性能优化。
1.2、构造函数 list(iterable)
q = list(range(5)) # [0, 1, 2, 3, 4]q = list("abc") # ['a', 'b', 'c']- 任何可迭代对象 → 列表;
- 复杂度 O(n),n 为去重后元素个数。
1.3、列表推导式 / 生成器表达式
# 列表推导式:一次性构造q = [x**2 for x in range(10) if x % 2 == 0]
# 生成器表达式:惰性迭代,先转 listq = list(x for x in range(1000000) if x % 997 == 0)- 语法灵活,可直接生成“队列初始数据”。
1.4、从其他容器转换
| 源容器 | 示例 | 备注 |
|---|---|---|
| tuple | list((1,2,3)) | 元组 → 列表 |
| set | list({1,2,3}) | 集合 → 列表(无序) |
| dict | list({'a':1,'b':2}) | 键视图 → 列表 |
| 文件对象 | list(open('file.txt')) | 每行一个元素 |
1.5、* 展开构造
# 合并多个可迭代对象q = [*range(3), *"abc", *[7, 8]] # [0, 1, 2, 'a', 'b', 'c', 7, 8]- Python 3.5+ 的解包语法,用于“一次性拼接”。
1.6、双端队列(deque)—— 官方推荐高性能队列
from collections import dequedq = deque() # 空双端队列dq = deque([1, 2, 3]) # 带初始数据dq = deque(maxlen=1000) # 环形缓冲区,满时自动丢弃最旧元素- 生产环境 推荐 collections.deque,因为它在两端插入/删除都是 O(1),而 list.insert(0, x) 是 O(n)。
- 完全兼容 list 的迭代、切片读取;
- 额外方法:appendleft, popleft, extendleft, rotate, maxlen。
1.7、线程安全队列(queue 模块)
import queueq = queue.Queue() # FIFOq = queue.Queue(maxsize=100) # 阻塞/非阻塞模式- 若需要在 多线程 中当真正队列,使用
queue.Queue/LifoQueue/PriorityQueue,内部用锁保护 - 与 list 接口不同:通过 put, get, task_done 管理。
1.8、性能对比速览
| 操作 | list | deque | Queue |
|---|---|---|---|
| 尾部 append | O(1) | O(1) | O(1) |
| 头部 insert | O(n) | O(1) | O(1) |
| 线程安全 | ❌ | ❌ | ✅ |
| 可切片 | ✅ | ✅ | ❌ |
- 临时/单线程:q = [] 或 q = list(iterable)。
- 高频两端操作:用 collections.deque(iterable)。
- 多线程安全:用 queue.Queue(maxsize)。
2、list.sort(*, key=None, reverse=False)
2.1、作用
- 原地重排:调用后原列表顺序被永久改变。
- 稳定排序:排序前后,相等元素的相对顺序不变。
- 多字段排序:可一次指定多个键,实现多级排序(例如先按年龄再按姓名)。
2.2、参数
| 形参 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| key | callable 或 None | None | 排序键函数。对每个元素调用 key(element),返回值作为排序依据。常见用法:- key=str.lower(忽略大小写)- key=lambda x: x.age(按对象属性)- key=operator.itemgetter(1)(按元组第二字段) |
| reverse | bool | False | 为 True 时降序排序;False 为升序。 |
2.3、返回值
- 始终返回 None。
- 因为排序是原地操作,所以 不返回新列表;若写 new_list = old_list.sort(),会得到 None,这是常见错误。
# 基础升序nums = [3, 1, 4, 1, 5]nums.sort()print(nums) # [1, 1, 3, 4, 5]
# 降序nums.sort(reverse=True)print(nums) # [5, 4, 3, 1, 1]
# 按字符串长度排序words = ['python', 'is', 'awesome']words.sort(key=len)print(words) # ['is', 'python', 'awesome']
# 多字段排序:先按成绩降序,再按姓名升序students = [('Alice', 90), ('Bob', 85), ('Alice', 95)]students.sort(key=lambda s: (-s[1], s[0]))print(students) # [('Alice', 95), ('Alice', 90), ('Bob', 85)]3、公共功能
3.1、运算符 +
- 相加,两个列表相加获取生成一个新的列表。
# + 运算a = [1] + [2, 3, ] + [4, 5]print(a)
b = [1, 2] + ["3", "4"]print(b)
# 输出结果[1, 2, 3, 4, 5][1, 2, '3', '4']3.2、运算符 *
- 相乘,列表*整型 将列表中的元素再创建N份并生成一个新的列表。
# * 运算a = [1, 2] * 3print(a)
# 输出结果[1, 2, 1, 2, 1, 2]3.3、索引(下标)取值
# 读user_list = ["范德彪","刘华强",'尼古拉斯赵四']print( user_list[0] )print( user_list[2] )print( user_list[3] ) # 报错
# 改user_list = ["范德彪","刘华强",'尼古拉斯赵四']user_list[0] = "刘德华"print(user_list) # ["刘德华","刘华强",'尼古拉斯赵四']
# 删user_list = ["范德彪","刘华强",'尼古拉斯赵四']del user_list[1]
user_list.remove("刘华强")ele = user_list.pop(1)3.4、切片取值
- 和字符串一样,列表也可以切片
- 使用语法:列表[start : end : step],获取列表中在 [start, end) 范围的子列表
- 注意范围 [start, end) 包含 start,不包含 end
- step 是步长,设为 n,则每隔 n 个元素获取一次
# 读user_list = ["范德彪","刘华强",'尼古拉斯赵四']print( user_list[0:2] ) # ["范德彪","刘华强"]print( user_list[1:] )print( user_list[:-1] )3.5、切片赋值
# 切片赋值a = ["1", "2", "3"]print(a)a[:] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] # 切片获取所有元素,并重新赋值print(a)
a[2:4] = [33, 44]print(a)
a[2:4] = [] # 相当于去掉第 3、4 个元素print(a)
a[:] = [] # 将 a 赋值为空列表print(a)
# 输出结果['1', '2', '3'][1, 2, 3, 4, 5, 6, 7, 8, 9, 0][1, 2, 33, 44, 5, 6, 7, 8, 9, 0][1, 2, 5, 6, 7, 8, 9, 0][]3.6、关键字 in
- 通过关键字 in 检查列表中是否包含指定元素,返回 bool 值
- not in 则是取反
# in、not ina = [1, 2, True, {"name": "周杰伦"}, ["how", "hi"]]
print(1 in a)print(3 in a)print({"name": "周杰伦"} in a)print(False not in a)
# 输出结果TrueFalseTrueTrue4、易错点
- 在 Python 里,一边遍历列表一边删元素是新手最容易踩的坑之一,核心问题是:列表是可变序列,删除操作会立即改变其长度和索引,从而让循环“错位”。下面用 3 个典型场景说明坑点、原因及正确做法。
4.1、漏删 / 跳过元素
lst = [1, 2, 2, 3]for i in lst: if i == 2: lst.remove(i) # 期望删掉所有 2print(lst) # 结果: [1, 2, 3] ← 还有一个 2 没删掉原因:第一次删下标 1 的 2 后,后面的元素整体前移,原下标 2 的 2 变成了下标 1,但 for 循环的指针已经走到下标 2 了,于是被跳过。
4.2、IndexError:索引越界
lst = [0, 1, 2]for i in range(len(lst)): del lst[i]# IndexError: list index out of range原因:列表长度在循环中不断缩短,而 range(len(lst)) 一开始就把长度固定为 3,当 i=2 时列表只剩 1 个元素,导致越界。
4.3、隐藏的副作用(列表推导式同理)
lst = [1, 2, 3, 4]new = [x for x in lst if lst.remove(x) or True] # 看似技巧,实则灾难# new == [1, 2, 3, 4],lst 被清空了原因:list.remove 会就地修改原列表,列表推导式内部迭代器仍在同一列表上工作,行为完全不可预测。
4.4、✅ 正确姿势
| 方法 | 代码示例 | 说明 |
|---|---|---|
| 倒序遍历 | for i in range(len(lst)-1, -1, -1): if cond: del lst[i] | 删除不影响未遍历索引 |
| 列表拷贝 | for x in lst[:]: if cond: lst.remove(x) | 遍历副本,修改原列表 |
| 生成新列表 | lst = [x for x in lst if not cond] | 函数式写法,最简洁 |
| filter | lst = list(filter(lambda x: not cond, lst)) | 与推导式等价 |
4.5、一句话总结
- 不要在同一个可变列表上同时进行“遍历指针”和“长度修改”两类操作;要么倒序、要么遍历副本、要么生成新列表。
五、元组tuple
- 元组是不可变序列,通常用于储存异构数据的多项集(例如由enumerate() 内置函数所产生的二元组)。
- 元组也被用于需要同构数据的不可变序列的情况(例如允许存储到set 或dict 的实例)。
1、构造方法:
1.1、字面量括号法(最常用)
t1 = (1, 2, 3) # 带括号t2 = 1, 2, 3 # 括号可省,逗号是灵魂t3 = () # 空元组t4 = (42,) # 单元素必须加逗号,否则变成整数| 细节 | 说明 |
|---|---|
| 逗号是标识符 | 没有逗号,(42) 只是表达式分组,类型为 int。 |
| 性能 | CPython 在编译期直接生成 BUILD_TUPLE,O(1)。 |
1.2、内置构造函数 tuple(iterable=())
t5 = tuple() # 空元组t6 = tuple([1, 2, 3]) # 列表 → 元组t7 = tuple('abc') # 字符串 → ('a', 'b', 'c')t8 = tuple({'x': 1, 'y': 2}) # 字典 → 键视图元组| 形参 | 默认值 | 复杂度 |
|---|---|---|
| iterable | () | O(n),n 为元素个数 |
1.3、生成器 / 推导式转元组
t9 = tuple(i**2 for i in range(5)) # 生成器表达式t10 = tuple([i for i in [1, 2, 2] if i % 2]) # 列表推导式- 生成器表达式惰性迭代,节省一次中间列表内存。
1.4、序列拆包与星号展开
a, b, *c = [1, 2, 3, 4] # c -> [3, 4]t11 = (*range(3), *"abc") # 拆包后合并 -> (0, 1, 2, 'a', 'b', 'c')- Python 3.5+ 支持
*解包语法,编译器生成BUILD_TUPLE_UNPACK。
1.5、嵌套元组(tuple 嵌套 tuple)
nested = ((1, 2), (3, 4), (5, 6))- 完全合法,用于 坐标点、树节点、多维索引 等场景。
1.6、命名元组(namedtuple)—— 语义元组
from collections import namedtuplePoint = namedtuple('Point', ['x', 'y'])p = Point(1, 2) # Point(x=1, y=2)- 仍是
tuple子类,但带字段名,提升可读性。
1.7、单元素陷阱与布尔陷阱
| 表达式 | 结果类型 | 说明 |
|---|---|---|
(42) | int | 无逗号,只是括号分组 |
(42,) | tuple | 有逗号,单元素元组 |
bool(()) | False | 空元组为假 |
bool((0,)) | True | 非空即真 |
1.8、性能对比
| 构造方式 | 时间复杂度 | 额外内存 | 备注 |
|---|---|---|---|
字面量 (1, 2, 3) | O(1) | 0 | 字节码直接生成 |
tuple(iterable) | O(n) | O(n) | 需要遍历一次 |
| 生成器表达式 | O(n) | 0(惰性) | 无中间列表 |
1.9、线程安全
- 元组 不可变,天然线程安全,可作为
dict键或set元素。
2、公共功能
2.1、运算符 +
- 使用运算符 + 连接多个元组
# +tup1 = (1,)tup2 = (2, 3)print(tup1 + tup2)
# 输出结果(1, 2, 3)2.2、运算符 *
- 使用运算符 * 将元组的元素重复
# *tup = (1, 2)print(tup * 2)
# 输出结果(1, 2, 1, 2)2.3、索引(下标)取值
# 索引tup = [1, 2, 3, 4, 5]print(tup[0])print(tup[-1])print(tup[2])
# 输出结果1532.4、切片取值
- 和列表一样,元组也可以切片
- 使用语法:元组[start : end : step],获取元组中在 [start, end) 范围的子元组
- 注意范围 [start, end) 包含 start,不包含 end
- step 是步长,设为 n,则每隔 n 个元素获取一次
# 切片tup = [1, 2, 3, 4, 5, 6, 7, 8]print(tup[:]) # 取全部元素print(tup[0:]) # 取全部元素print(tup[2:5]) # 取第 3 个元素到第 5 个元素print(tup[::-1]) # 倒序取所有元素print(tup[-3:-1]) # 取倒数第 3 个元素到倒数第 2 个元素
# 输出结果[1, 2, 3, 4, 5, 6, 7, 8][1, 2, 3, 4, 5, 6, 7, 8][3, 4, 5][8, 7, 6, 5, 4, 3, 2, 1][6, 7]2.5、关键字 in
- 通过关键字 in 检查元组中是否包含指定元素,返回 bool 值
- not in 则是取反
# intup = (1, 2, 3)print(1 in tup)print(22 not in tup)
# 输出结果TrueTrue六、range 对象
range是一个 不可变序列,用来生成 等差整数序列。它既节省内存又支持随机访问,是 for 循环 和 索引切片 最常用的工具之一
1、构造方法
range(stop)range(start, stop)range(start, stop, step)- 仅限位置参数,无关键字参数。
- 三种重载在 CPython 中最终都会归一到 (start, stop, step) 三元组。
2、参数语义与默认值
| 形参 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| start | int | 0 | 序列起始值(包含) |
| stop | int | — 必填 | 序列终止值(不包含) |
| step | int | 1 | 步长,可为负;不能为 0 |
- 若
step为负,序列 递减。 - 所有参数必须是 整数(Python 3 起拒绝 float)。
3、返回值
- range 对象(lazy):不立即生成列表,仅保存
(start, stop, step)。 - 支持 索引、切片、len()、成员检测且均为 O(1)。
- 要得到列表需显式转换:
list(range(...)),复杂度 O(n)。
4、行为示例
>>> list(range(5)) # 0..4[0, 1, 2, 3, 4]
>>> list(range(2, 7)) # 2..6[2, 3, 4, 5, 6]
>>> list(range(1, 10, 2)) # 1,3,5,7,9[1, 3, 5, 7, 9]
>>> list(range(5, 0, -1)) # 5,4,3,2,1[5, 4, 3, 2, 1]
>>> range(0, 5)[::-1] # 切片反转 → range(4, -1, -1)range(4, -1, -1)5、性能与内存
- 存储仅需 3 个整数(start, stop, step),与长度无关。
len(range)计算为 max(0, (stop-start+step-1)//step),O(1)。- 遍历速度接近 C 级循环,比生成器更快。
6、常见误区
| 错误写法 | 原因 |
|---|---|
range(5.0) | TypeError: ‘float’ object cannot be interpreted as an integer |
range(5, 10, 0) | ValueError: step argument must not be zero |
range(10)[10] | IndexError: range object index out of range |
7、==、!= 操作
- 使用 == 和 != 检测 range 对象是否相等是将其作为序列来比较。也就是说,如果两个 range 对象表示相同的值序列就认为它们是相等的。(请注意比较结果相等的两个 range 对象可能会具有不同的start, stop和step 属性,例如 range(0) == range(2, 1, 3) 而 range(0, 3, 2) == range(0, 4, 2)。)