Python容器:特性、区别和使用场景

一、容器分类

Python容器是用于存储和组织多个数据元素的数据结构。根据其存储方式和访问特性,可分为三大类:

1.1 序列类型(Sequence)

  • 特点:元素按顺序存储,支持通过整数索引访问
  • 代表:列表(List)、元组(Tuple)、字符串(String)、字节(Byte)、字节数组(ByteArray)

1.2 映射类型(Mapping)

  • 特点:键值对存储,通过唯一键访问值
  • 代表:字典(Dict)、默认字典(defaultdict)、有序字典(OrderedDict)

1.3 集合类型(Set)

  • 特点:无序、元素唯一,支持数学集合运算
  • 代表:集合(Set)、冻结集合(Frozenset)

二、基础内置容器

2.1 列表(List)

核心特性

  • 可变性:可变容器,支持增删改操作
  • 有序性:保持元素插入顺序(Python 3.7+正式规范)
  • 元素类型:支持任意数据类型混合存储
  • 索引方式:通过整数索引访问(0-based)
  • 可哈希性:不可哈希,不能作为字典的键

常用操作

python 复制代码
# 创建
lst = [1, "hello", 3.14, [4, 5]]
empty_lst = []
list_from_iterable = list(range(5))  # [0, 1, 2, 3, 4]

# 访问
print(lst[0])  # 1
print(lst[-1])  # [4, 5]
print(lst[1:3])  # ["hello", 3.14]

# 修改
lst[0] = 100
lst.append(6)  # 尾部添加
lst.insert(1, "world")  # 指定位置插入
lst.extend([7, 8])  # 扩展列表
lst.pop()  # 尾部删除并返回
lst.remove("hello")  # 删除第一个匹配元素
del lst[0]  # 删除指定索引元素

性能特点

  • 尾部添加/删除:O(1)
  • 中间插入/删除:O(n)
  • 随机访问:O(1)
  • 查找元素:O(n)

适用场景

  • 需要频繁修改元素的序列
  • 元素顺序重要的场景
  • 简单的数据存储和遍历

2.2 元组(Tuple)

核心特性

  • 可变性:不可变容器,创建后不能修改
  • 有序性:保持元素插入顺序
  • 元素类型:支持任意数据类型混合存储
  • 索引方式:通过整数索引访问
  • 可哈希性:当所有元素都可哈希时,元组可哈希

常用操作

python 复制代码
# 创建
tpl = (1, "hello", 3.14)
single_element_tpl = (5,)  # 注意逗号
empty_tpl = ()
tuple_from_iterable = tuple([1, 2, 3])

# 访问
print(tpl[0])  # 1
print(tpl[-1])  # 3.14
print(tpl[1:3])  # ("hello", 3.14)

# 元组不可变,但包含的可变元素可以修改
nested_tpl = (1, [2, 3])
nested_tpl[1].append(4)  # 合法,修改的是列表而非元组本身

性能特点

  • 比列表更轻量,内存占用更小
  • 创建速度比列表快约30%
  • 不可变性带来线程安全
  • 可作为字典的键或集合的元素

适用场景

  • 存储不需要修改的数据集合
  • 作为函数返回值返回多个结果
  • 作为字典的键或集合的元素
  • 保护数据不被意外修改

2.3 字典(Dictionary)

核心特性

  • 可变性:可变容器,支持增删改操作
  • 有序性:Python 3.7+保持插入顺序
  • 元素类型:键必须可哈希,值可以是任意类型
  • 索引方式:通过键访问值
  • 可哈希性:不可哈希

常用操作

python 复制代码
# 创建
d = {"name": "Alice", "age": 25, "city": "New York"}
empty_d = {}
dict_from_pairs = dict([("a", 1), ("b", 2)])

# 访问
print(d["name"])  # Alice
print(d.get("gender", "unknown"))  # unknown,避免KeyError

# 修改
d["age"] = 26
d["gender"] = "female"  # 添加新键值对
del d["city"]  # 删除键值对
age = d.pop("age")  # 删除并返回值
keys = d.keys()  # 获取所有键
values = d.values()  # 获取所有值
items = d.items()  # 获取所有键值对

性能特点

  • 基于哈希表实现,平均时间复杂度O(1)
  • 插入、删除、查找操作都非常高效
  • 内存开销比列表大

适用场景

  • 需要快速查找、插入和删除的场景
  • 存储键值对数据
  • 表示结构化数据(如JSON对象)
  • 计数和频率统计

2.4 集合(Set)

核心特性

  • 可变性:可变容器,支持增删改操作
  • 有序性:无序(Python 3.7+不保证顺序)
  • 元素类型:元素必须可哈希且唯一
  • 索引方式:不支持索引访问
  • 可哈希性:不可哈希

常用操作

python 复制代码
# 创建
s = {1, 2, 3, 4, 5}
empty_s = set()  # 注意:{}创建的是空字典
set_from_iterable = set([1, 2, 2, 3, 3, 3])  # {1, 2, 3}

# 修改
s.add(6)  # 添加元素
s.update([7, 8])  # 添加多个元素
s.remove(3)  # 删除元素,不存在则抛出KeyError
s.discard(10)  # 删除元素,不存在则不报错
element = s.pop()  # 随机删除并返回一个元素
s.clear()  # 清空集合

# 集合运算
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}
print(a ^ b)  # 对称差集 {1, 2, 5, 6}

性能特点

  • 基于哈希表实现,平均时间复杂度O(1)
  • 成员检查非常高效
  • 自动去重

适用场景

  • 去重操作
  • 成员关系检查
  • 数学集合运算(并、交、差等)
  • 快速查找

2.5 冻结集合(Frozenset)

核心特性

  • 可变性:不可变容器,创建后不能修改
  • 有序性:无序
  • 元素类型:元素必须可哈希且唯一
  • 索引方式:不支持索引访问
  • 可哈希性:可哈希

常用操作

python 复制代码
# 创建
fs = frozenset([1, 2, 3, 4, 5])

# 支持所有集合的非修改操作
print(fs & {3, 4, 5})  # frozenset({3, 4, 5})
print(3 in fs)  # True

适用场景

  • 需要不可变集合的场景
  • 作为字典的键或另一个集合的元素
  • 保护集合数据不被修改

2.6 字符串(String)

核心特性

  • 可变性:不可变容器,创建后不能修改
  • 有序性:保持字符顺序
  • 元素类型:只能存储Unicode字符
  • 索引方式:通过整数索引访问
  • 可哈希性:可哈希

常用操作

python 复制代码
# 创建
s = "Hello, World!"
empty_s = ""
multi_line_s = """Line 1
Line 2
Line 3"""

# 访问
print(s[0])  # H
print(s[-1])  # !
print(s[7:12])  # World

# 字符串操作(返回新字符串)
upper_s = s.upper()  # HELLO, WORLD!
lower_s = s.lower()  # hello, world!
split_s = s.split(", ")  # ["Hello", "World!"]
joined_s = "-".join(["a", "b", "c"])  # a-b-c
replaced_s = s.replace("World", "Python")  # Hello, Python!

性能特点

  • 字符串操作非常高效
  • 不可变性带来线程安全
  • 支持丰富的内置方法

适用场景

  • 文本处理
  • 数据序列化
  • 表示标识符和常量

2.7 字节(Byte)与字节数组(ByteArray)

核心特性

  • bytes:不可变字节序列
  • bytearray:可变字节序列
  • 元素类型:只能存储0-255之间的整数
  • 有序性:保持字节顺序
  • 索引方式:通过整数索引访问
  • 可哈希性:bytes可哈希,bytearray不可哈希

常用操作

python 复制代码
# bytes
b = b"Hello, World!"
empty_b = b""
b_from_list = bytes([72, 101, 108, 108, 111])  # b'Hello'

# bytearray
ba = bytearray(b"Hello")
ba[0] = 74  # 修改为J
print(ba)  # bytearray(b'Jello')

适用场景

  • 二进制数据处理
  • 网络通信
  • 文件I/O操作
  • 与C语言交互

三、collections模块高级容器

Python标准库的collections模块提供了多种增强型容器,解决了基础容器的一些局限性。

3.1 deque(双端队列)

核心特性

  • 双向队列,支持两端高效添加和删除操作
  • 可指定最大长度,超出时自动删除另一端元素
  • 线程安全

常用操作

python 复制代码
from collections import deque

# 创建
dq = deque([1, 2, 3], maxlen=5)

# 两端操作
dq.append(4)  # 右端添加
dq.appendleft(0)  # 左端添加
dq.pop()  # 右端删除并返回
dq.popleft()  # 左端删除并返回

# 其他操作
dq.extend([5, 6])
dq.extendleft([-1, -2])  # 注意:结果是[-2, -1, 1, 2, 3]
dq.rotate(1)  # 向右旋转1位

性能特点

  • 两端添加/删除:O(1)
  • 中间操作:O(n)
  • 比列表的pop(0)和insert(0)高效得多

适用场景

  • 实现队列和栈
  • 滑动窗口
  • 最近最少使用(LRU)缓存

3.2 defaultdict(默认字典)

核心特性

  • 字典的子类,访问不存在的键时自动创建默认值
  • 默认值由工厂函数指定

常用操作

python 复制代码
from collections import defaultdict

# 创建
dd_int = defaultdict(int)  # 默认值为0
dd_list = defaultdict(list)  # 默认值为空列表
dd_set = defaultdict(set)  # 默认值为空集合

# 使用
dd_int["a"] += 1  # 不会抛出KeyError,自动创建"a": 0然后加1
dd_list["fruits"].append("apple")  # 自动创建"fruits": []然后添加元素

适用场景

  • 分组数据
  • 计数
  • 构建嵌套数据结构

3.3 OrderedDict(有序字典)

核心特性

  • 字典的子类,严格保持元素插入顺序
  • 支持根据插入顺序进行操作

常用操作

python 复制代码
from collections import OrderedDict

# 创建
od = OrderedDict()
od["a"] = 1
od["b"] = 2
od["c"] = 3

# 操作
od.move_to_end("a")  # 将"a"移到末尾
od.move_to_end("c", last=False)  # 将"c"移到开头
key, value = od.popitem()  # 删除并返回最后一个元素
key, value = od.popitem(last=False)  # 删除并返回第一个元素

注意:Python 3.7+内置字典已经保持插入顺序,但OrderedDict仍然有其独特的方法和用途。

适用场景

  • 需要严格控制元素顺序的场景
  • 实现LRU缓存
  • 序列化和反序列化时保持顺序

3.4 Counter(计数器)

核心特性

  • 字典的子类,用于计数可哈希对象
  • 元素作为键,计数作为值

常用操作

python 复制代码
from collections import Counter

# 创建
c = Counter("abracadabra")  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
c_list = Counter([1, 2, 2, 3, 3, 3])  # Counter({3: 3, 2: 2, 1: 1})

# 操作
print(c.most_common(2))  # [('a', 5), ('b', 2)]
c.update("aaa")  # 增加计数
c.subtract("aa")  # 减少计数
total = sum(c.values())  # 计算总计数

适用场景

  • 频率统计
  • 词频分析
  • 投票计数

3.5 NamedTuple(命名元组)

核心特性

  • 元组的子类,每个元素都有名称
  • 不可变,支持通过名称和索引访问元素
  • 比普通类更轻量

常用操作

python 复制代码
from collections import namedtuple

# 创建
Point = namedtuple("Point", ["x", "y"])
Person = namedtuple("Person", "name age gender")

# 使用
p = Point(10, 20)
print(p.x)  # 10
print(p[1])  # 20
print(p._asdict())  # {'x': 10, 'y': 20}

# 替换值(返回新的命名元组)
new_p = p._replace(x=100)

适用场景

  • 表示简单的数据结构
  • 替代没有方法的类
  • 提高代码可读性

四、性能对比

4.1 时间复杂度对比

容器类型 访问 查找 插入 删除
列表(List) O(1) O(n) O(n) O(n)
元组(Tuple) O(1) O(n) - -
字典(Dict) O(1) O(1) O(1) O(1)
集合(Set) - O(1) O(1) O(1)

注意:以上为平均时间复杂度,最坏情况下字典和集合的操作可能退化为O(n)。

4.2 内存占用对比

以下是存储100万个整数时不同容器的内存占用(近似值):

容器类型 内存占用(MB) 相对大小
列表(List) ~28 1.0x
元组(Tuple) ~20 0.7x
集合(Set) ~72 2.6x
字典(Dict) ~80 2.9x

说明

  • 元组比列表更节省内存,因为它不需要额外的空间来存储修改操作所需的信息
  • 字典和集合的内存开销较大,因为它们需要维护哈希表

五、容器选择指南

5.1 按需求选择容器

需求 推荐容器 不推荐容器
需要顺序存储并频繁修改 List Tuple, Set
需要顺序存储且不修改 Tuple List
需要快速查找、插入和删除 Dict, Set List
需要去重 Set List
需要键值对存储 Dict List, Tuple
需要两端高效操作 deque List
需要计数 Counter Dict
需要分组数据 defaultdict Dict
需要命名访问元素 NamedTuple Tuple
需要存储大量同类型数值 array.array List

5.2 建议

  1. 优先使用不可变容器:在不需要修改数据时,优先使用元组、冻结集合和字符串,它们更安全、更高效。

  2. 选择合适的字典变体

    • 需要默认值时使用defaultdict
    • 需要严格顺序时使用OrderedDict
    • 需要计数时使用Counter
  3. 避免在循环中修改列表:在循环中修改列表可能导致意外行为和性能问题。如果需要过滤或转换列表,使用列表推导式。

  4. 使用集合进行成员检查:如果需要频繁检查元素是否存在,使用集合而不是列表,集合的成员检查是O(1)时间复杂度。

  5. 使用deque实现队列和栈:deque比列表更适合实现队列和栈,特别是当需要在两端进行操作时。

  6. 使用NamedTuple提高代码可读性:当需要表示简单的数据结构时,使用NamedTuple而不是普通元组,它可以通过名称访问元素,提高代码可读性。

  7. 注意字典的内存开销:字典的内存开销较大,如果只需要存储键而不需要值,使用集合。

六、注意事项

  1. 列表的浅拷贝问题

    python 复制代码
    a = [1, 2, [3, 4]]
    b = a.copy()  # 浅拷贝
    b[2].append(5)
    print(a)  # [1, 2, [3, 4, 5]],a也被修改了

    解决方法:使用copy.deepcopy()进行深拷贝。

  2. 字典的键必须可哈希

    列表、字典和集合等不可哈希对象不能作为字典的键。如果需要使用复杂对象作为键,可以使用元组或自定义类并实现__hash____eq__方法。

  3. 集合的元素必须可哈希

    与字典的键类似,集合的元素也必须可哈希。

  4. 字符串的不可变性

    字符串是不可变的,所有修改字符串的操作都会返回新的字符串。频繁修改字符串会导致性能问题,此时可以使用bytearrayio.StringIO

  5. 空集合的创建
    {}创建的是空字典,而不是空集合。创建空集合应该使用set()

  6. 元组的单元素问题

    单元素元组必须在元素后面加逗号,否则会被解释为普通表达式。

    python 复制代码
    t = (5)  # 这是整数5,不是元组
    t = (5,)  # 这才是单元素元组

七、总结

  • 序列类型适合存储有序数据,其中列表用于可变数据,元组用于不可变数据
  • 映射类型适合存储键值对数据,提供快速的查找、插入和删除操作
  • 集合类型适合存储唯一元素,支持高效的成员检查和数学集合运算
  • collections模块提供了多种增强型容器,解决了基础容器的一些局限性
相关推荐
计算机安禾6 小时前
【c++面向对象编程】第27篇:空类的大小为什么是1?——C++对象标识的秘密
开发语言·c++·算法
我不是8神6 小时前
面试题:Gorutine泄露的条件有哪些?
java·开发语言
奇树谦6 小时前
QListView和QListWidget区别详细说明
开发语言
郭龙_Jack6 小时前
Java并发包(JUC)深度解析:从LockSupport到云原生演进
开发语言·云原生·java并发编程
AC赳赳老秦6 小时前
OpenClaw与思维导图工具联动:自动生成工作规划脑图、拆解任务节点,适配职场管理
java·大数据·服务器·数据库·python·php·openclaw
才兄说6 小时前
机器人二次开发机器人动作定制?数据优化迁移
python
Highcharts.js6 小时前
AI向量知识谱系图表创建示例代码|Highcharts网络图表(networkgraph)搭建案例
开发语言·前端·javascript·网络·信息可视化·编辑器·highcharts
周杰伦fans6 小时前
C# AutoCAD 二次开发极简入门:从环境搭建到高效实战
开发语言·c#
hhb_6186 小时前
Swift技术难点梳理与实战案例解析
开发语言·ios·swift