在Python的世界里,我们每天都在和 listtupledictset 打交道。但你有没有想过:

  • 为什么元组比列表快?
  • 字典为什么查找那么高效?
  • 列表扩容背后的策略是什么?
  • set 真的是“无序”的吗?

这些看似基础的问题,其实都源于Python对数据类型的底层设计。接下来,我们就来深入剖析Python中常见数据类型的本质,从“序列”、“容器”这些抽象概念讲起,到 listtupledictset 的对比实践,以及最后的相关的底层实现原理。

理清概念:这些“类型名词”是什么

刚开始学Python时,你可能听过这些术语:list 是可变序列”、“str 是扁平序列”、“dict 是容器类型……听起很玄乎,其实他们只是从不同视角对数据结构的分类,我们来一一拆解。

1. 序列类型(Sequence)

提到 “序列”,你可以先联想日常生活中的 “排队”—— 每个人(数据)按顺序站好,有自己的位置(索引),这就是序列的核心特征。

什么是序列?
广义上,序列是一种连续存储的数据格式,就像把书按顺序摆放在书架的同一层,每个数据都有固定的 “位置编号”,通过编号能快速找到对应数据。简单说,序列就是一组按照顺序排列的数据集合。

什么是Python中的序列类型?

Python对序列做了一层抽象封装,形成了“序列类型”,只要某个数据类型支持一下操作,就可以归为序列类型:

  • 支持索引访问:seq[0]
  • 支持切片操作:seq[1:3]
  • 支持连接与重复:seq1 + seq2seq * 3
  • 支持成员判断:x in seq

Python 中常见的序列类型有:list(列表)、tuple(元组)、str(字符串)、bytes(字节串)、array(数组)。

1
2
3
4
5
# 序列通用操作示例
demo_list = [1, 2, 3, 4, 5]
print(demo_list[2]) # 索引: 3
print(demo_list[1:4]) # 切片: [2, 3, 4]
print(3 in demo_list) # 成员检测: True

序列类型的两种分类方式

  • 可变序列 vs 不可变序列
类型 特点 示例
可变序列(MutableSequence) 可以动态增删改 list, bytearray, array.array
不可变序列(Sequence) 一旦创建就不能修改,任何“修改”操作都会返回新对象 tuple, str, bytes

Tips:tuple虽然是不可变,但如果它内部包含list,你依然可以修改那个list,因为 tuple只保证“引用不变”,不保证“内容不变”。

  • 容器序列 vs 扁平序列
类型 存储内容 特点 示例
容器序列 存储对象的引用 可以嵌套任意类型 list, tuple
扁平序列 存储对象的值本身 更紧凑,效率更高 str, bytes, array

举个例子:['a', 'b', 'c'] 中每个元素都是对字符串对象的引用;而 'abc' 是连续的字符值存储在内存中,更节省空间。

2. 数值类型(Number)

数值类型是Python中最基础的数据类型之一,专门用来表示数字,主要包括三类:

  • 整数(int):比如 10-50,Python 的 int 没有大小限制,能存非常大的数(比如 10**100 这样的 “天文数字”)。
  • 布尔值(bool):特殊的 int 子类,只有 True(等价于 1)和 False(等价于 0)两个值,常用于条件判断。
  • 浮点数(float):带小数点的数字,比如 3.14-0.5,注意浮点数有精度限制(比如 0.1 + 0.2 不等于 0.3)。
  • 复数(complex):形如 a + bj 的数字(j 是虚数单位),比如 2 + 3j,主要用于科学计算场景。

需要注意的是,它们不属于“容器”,因为不包含其他对象,是原子性的。

3. 容器类型(Container)

这里的 “容器类型” 和前面提到的 “容器序列” 不一样 —— 它是从 “功能” 角度分类,指所有能 “容纳其他对象” 的数据结构。简单说,只要一个数据类型能把多个对象 “打包” 在一起,就是容器类型。

  • 容器序列:特指 listtuple 这类支持索引的序列。

  • 容器类型:泛指能容纳其他对象的数据结构,比如 dictsetlist

Python 中最常用的容器类型有 4 个:

  • list(列表):可变、有序,能存任意类型数据
  • tuple(元组):不可变、有序,能存任意类型数据
  • dict(字典):可变、键值对结构,3.7 + 后有序
  • set(集合):可变、无序,元素唯一

接下来我们就聚焦这 4 个核心容器类型,从语法、原理、性能到场景,做一次全方位对比。

list vs tuple:动态与静态的选择

基本用法对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# list: 可变,灵活
demo_list = ['AA', 'BB', 3, True]
demo_list = [] # 创建、初始化
demo_list = list() # 创建、初始化
demo_list.append(3.14) # 添加元素

# tuple: 不可变,稳定
demo_tuple = tuple() # 空元组
demo_tuple = (1,) # 单元素元组必须有逗号
demo_tuple = (1, 'hello', True) # 多元素元组
demo_tuple[0] = 2 # 报错!不支持修改

# tuple"新增"实际是创建新元组
demo_tuple_raw= (1, 2.0, "a")
demo_tuple_new = demo_tuple_raw + (True,) # 创建新元组 (1, 2.0, 'a', True)
特性 list tuple
是否可变
是否支持增删改
是否支持索引/负数索引/切片
内存占用 较大 较小
创建速度
是否相互转换 tuple() list()

list的实现机制

Python的list实际上是一个动态扩容顺序表,采用“分离式结构”:

  • 表头(对象元信息)和数据区分开存储。
  • 初始分配8个元素空间,不够时自动扩容。

扩容策略:

  • 小列表(<50000):扩容为原来的 4倍
  • 大列表(≥50000):扩容为原来的 2倍

这种“过度分配”(over-allocation)策略保证了 append() 操作的均摊时间复杂度为 O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
demo_list = []
print(demo_list.__sizeof__()) # 40 字节(空列表)

demo_list.append(1)
print(demo_list.__sizeof__()) # 72 → 分配了4个元素的空间 (72-40)/8 = 4

demo_list.extend(range(4)) # 加满4个
print(demo_list.__sizeof__()) # 72

demo_list.append(5)
print(demo_list.__sizeof__()) # 104 → 扩容!


def check_memory_usage():
"""查看列表动态扩容过程"""
lst = []
print(f"空列表大小: {lst.__sizeof__()} bytes")

for i in range(10):
lst.append(i)
print(f"添加{i}后: {lst.__sizeof__()} bytes, 元素数: {len(lst)}")

check_memory_usage()

tuple的实现机制

tuple 使用“一体式结构”的顺序表,创建后大小固定,不能扩容。

正因为不可变,Python 可以对小元组进行缓存。比如 (1,2,3) 第一次创建后会被缓存,下次再创建相同的元组,直接复用内存,极大提升性能

list 与 tuple 性能对比

使用timeit模块进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建 速度对比
python3 -m timeit 'x=(1,2,3,4,5,6)'
# 20000000 loops, best of 5: 9.97 nsec per loop

python3 -m timeit 'x=[1,2,3,4,5,6]'
# 5000000 loops, best of 5: 50.1 nsec per loop

# 访问 速度对比
python3 -m timeit -s 'x=[1,2,3,4,5,6]' 'y=x[3]'
# 10000000 loops, best of 5: 22.2 nsec per loop

python3 -m timeit -s 'x=(1,2,3,4,5,6)' 'y=x[3]'
# 10000000 loops, best of 5: 21.9 nsec per loop

结论:tuple的创建速度更优,访问速度略快。其原因在于:

  • 静态结构,无需维护扩容信息
  • Python缓存常用tuple,减少内存的分配开销
  • 更少的功能意味着更小的开销

应用场景选择

场景 推荐类型
存储固定数据(如坐标、配置) tuple
函数返回多个值 tuplereturn x, y
需要频繁增删改的数据 list
用作字典的键 tuple(不可变)
作为集合元素 tuple
  • 使用元组的场景
1
2
3
4
5
6
7
8
9
10
11
12
# 1. 返回经纬度坐标
def get_location():

return (longitude, latitude) # 使用元组保证数据不可变

# 2. 函数返回多个值
def get_user_info(user_id):
# 返回用户姓名、年龄、邮箱
return ("BluesSen", 32, "bluessen@email.com")

# 3. 配置信息存储
DATABASE_CONFIG = ("localhost", 3306, "my_db", "user", "password")
  • 使用列表的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 记录用户一周内看过的帖子ID
viewer_owner_id_list = []
records = queryDB(viewer_id) # 查询数据库
for record in records:
viewer_owner_id_list.append(record.id) # 动态添加元素

# 2. 动态数据收集
user_actions = [] # 收集用户操作日志

def log_action(action):
user_actions.append(action) # 动态添加

# 3. 需要修改的数据
shopping_cart = ["apple", "banana", "milk"]
shopping_cart.remove("banana") # 修改购物车
shopping_cart.sort() # 排序

常用操作汇总

list操作:

  • index():返回指定元素下标
  • count():统计元素出现次数
  • len():获取列表长度
  • append():末尾追加元素
  • extend():扩展列表(添加序列中的所有元素)
  • insert():指定位置插入元素
  • pop():删除并返回指定位置元素(默认最后)
  • remove():移除第一个匹配项
  • clear():清空列表
  • reverse()sort():原地反转和排序

tuple操作:

  • index():查找元素位置
  • count():统计元素出现次数
  • len():获取元组长度

通用函数:

  • reversed():返回反转后的迭代器
  • sorted():返回排序后的新列表

注意:如果想给元组排序 / 反转,可以用全局函数 sorted()reversed(),它们会返回新列表 / 迭代器,不修改原元组:

1
2
3
t = (3,1,2)
sorted_t = sorted(t) # 输出 [1,2,3](列表)
reversed_t = list(reversed(t)) # 输出 [2,1,3](转成列表)

dict vs set:键值对与去重利器

基本用法对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 字典:键值对
demo_dict = {} # 空字典
demo_dict = dict() # 空字典
user = {'name': 'Alice', 'age': 25, 'city': 'Beijing'}

# 集合:唯一元素
demo_set = set() # 空集合只能用set()
demo_set = {10, 'aa', 20.0, False, 50}
tags = {'python', 'web', 'backend'}

# 成员检测效率对比
large_list = list(range(1000000))
large_set = set(large_list)

# 列表检测: O(n)时间复杂度
# 集合检测: O(1)时间复杂度
  • 关键区别:空字典用 {}dict(),空集合只能用 set()—— 因为 {} 优先被解析为字典。

核心特性对比

特性 Dict(字典) Set(集合)
存储结构 键值对(key: value) 单个元素(无键值)
元素唯一性 key 唯一,value 可重复 所有元素唯一(自动去重)
有序性 3.7+ 有序(插入顺序) 无序(无法通过索引访问)
索引访问 通过 key 访问,如 d['key'] 无序,无索引
核心用途 高效查询(通过 key 找 value) 去重、集合运
时间复杂度(查/增/删) O(1) O(1)

哈希表结构演变

老版本结构(紧凑但低效):

1
2
3
4
5
6
+-------------------------------+
| hash | key | value |
+-------------------------------+
| 123 | 'a' | 10 |
| 456 | 'b' | 20 |
+-------------------------------+

新版本结构(稀疏索引 + 紧凑存储):

1
2
3
4
5
6
7
8
9
Indices: [None, 0, None, None, 1, ...]

Entries:
+------------------+
| hash | key | val |
+------------------+
| 123 | 'a' | 10 |
| 456 | 'b' | 20 |
+------------------+

优势:节省内存、提升遍历速度、支持有序迭代。

dict和set 的实现机制

dictset 的核心都是哈希表(Hash Table),通过哈希函数将键映射到数组索引,实现近乎常数时间的查找。Python采用优化后的哈希表结构,提高了内存利用率和访问效率。

  • dict 的哈希表结构:存储“哈希值(hash)、键(key)、值(value)”三个元素,支持快速键值查找
text
1
2
3
4
5
6
7
# 索引区(记录元素在数据区的位置)
Indices: [None, 0, None, 2, None, 1, ...]
# 数据区(存储实际的键值对和哈希值)
Entries:
[hash0, key0, value0] # 位置0
[hash1, key1, value1] # 位置1
[hash2, key2, value2] # 位置2
  • set 只存储 “哈希值(hash)、元素(element)”,因为没有 value,结构更简单, 专注于快速成员检测
text
1
2
3
4
Entries:
[hash0, element0] # 位置0
[hash1, element1] # 位置1
[hash2, element2] # 位置2

操作分析:

  1. 插入操作
    • 计算键的哈希值并与mask做与操作得到位置index
    • 如果位置空,直接插入
    • 如果位置被占用,比较哈希值和键
    • 都相等:更新值(字典)或忽略(集合)
    • 不相等(哈希冲突):继续寻找空位
  2. 查找操作
    • 类似插入过程,找到对应位置后比较哈希值和键
  3. 删除操作
    • 标记删除位置,等待哈希表调整时真正删除

哈希冲突与扩容机制

什么是哈希冲突?

  • 两个不同的键,计算出相同的哈希值(或索引),就会发生冲突。

  • Python采用开放寻址法解决冲突:如果位置被占,就找下一个空位。这可能会降低操作效率。为了保证高效性,哈希表始终保持至少1/3的剩余空间,当空间不足时自动扩容。

如何实现扩容机制

  • 当哈希表使用率 > 2/3 时,触发扩容。

  • 扩容为原来的 2~4 倍,并重新哈希所有元素。

这也是为什么 dict 插入操作虽然是 O(1),但偶尔会“卡一下”——那是它在扩容。

应用实践与技巧

  • dict的用法实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 字典推导式
squares = {x: x*x for x in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 2. 合并字典
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
merged = {**dict1, **dict2} # {'a': 1, 'b': 3, 'c': 4}

# 3. 设置默认值
from collections import defaultdict
word_count = defaultdict(int)
for word in words:
word_count[word] += 1

# 4. 字典排序示例
d = {'b': 1, 'a': 2, 'c': 10}

# 5. 按key排序
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0])
# [('a', 2), ('b', 1), ('c', 10)]

# 6. 按value排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1])
# [('b', 1), ('a', 2), ('c', 10)]
  • dict的场景实践
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 配置管理
app_config = {
"debug": True,
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"api_keys": {
"google": "abc123",
"aws": "def456"
}
}

# 2. 数据聚合
def count_words(text):
"""统计词频"""
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
return word_count
  • set的用法实践
1
2
3
4
5
6
7
8
9
10
11
# 1. 集合运算
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}

print(A | B) # 并集: {1, 2, 3, 4, 5, 6}
print(A & B) # 交集: {3, 4}
print(A - B) # 差集: {1, 2}

# 2. 快速去重
numbers = [1, 2, 2, 3, 4, 4, 5]
unique = list(set(numbers)) # [1, 2, 3, 4, 5]
  • set 的场景实践
1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 权限管理
user_roles = {"admin", "editor", "viewer"}
current_user_roles = {"editor"}

# 检查权限
if current_user_roles & user_roles:
print("有访问权限")

# 2. 数据清洗
def remove_duplicates(data):
"""快速去重并保持顺序"""
seen = set()
return [x for x in data if not (x in seen or seen.add(x))]

常用操作汇总

dict 操作:

  • keys():返回所有键
  • values():返回所有值
  • items():返回所有键值对
  • del:删除键值对
  • clear():清空字典
  • 遍历:for key in dictfor key, value in dict.items()

set 操作:

  • add():添加元素
  • update():添加多个元素(传入序列)
  • remove():删除指定元素(不存在则报错)
  • discard():删除指定元素(不存在不报错)
  • pop():随机删除并返回一个元素(因集合无序需谨慎使用)

如何选择合适的数据类型?

场景1:频繁查找

1
2
3
4
5
6
7
8
9
# 错误做法:使用列表查找
names_list = ["Alice", "Bob", "Charlie"] * 1000
if "David" in names_list: # O(n)时间复杂度
pass

# 正确做法:使用集合查找
names_set = set(names_list)
if "David" in names_set: # O(1)时间复杂度
pass

场景2:数据记录

1
2
3
4
5
# 需要修改数据:用列表
student_scores = [85, 92, 78] # 可能变化

# 不需要修改数据:用元组
student_info = ("张三", "2021001", "计算机系") # 固定信息

场景3:内存使用优化

1
2
3
4
5
6
7
8
# 使用__sizeof__()分析内存占用
import sys

data_list = [1, 2, 3, 4, 5]
data_tuple = (1, 2, 3, 4, 5)

print(f"列表占用: {sys.getsizeof(data_list)} bytes")
print(f"元组占用: {sys.getsizeof(data_tuple)} bytes")

总结一下

场景 推荐数据结构 原因
动态数据集合 列表(list) 支持频繁增删改
固定数据集合 元组(tuple) 更快的创建和访问速度
键值映射 字典(dict) 快速的键值查找
唯一值集合 集合(set) 快速成员检测和去重
配置信息 元组/字典 根据是否需要修改选择
数据缓存 字典 快速的键值访问

记住这几个原则:

  1. 需要修改 → 选择列表
  2. 不需要修改 → 选择元组
  3. 需要快速查找 → 选择字典或集合
  4. 需要去重 → 选择集合
  5. 需要保持顺序 → 选择列表或元组

理解了这些数据结构的底层原理和特性,能够帮助我们在实际开发中做出更合理的选择,编写出更高效、更优雅的Python代码。


本站由 BluesSen 使用 Stellar 1.33.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站总访问量