一、容器分类
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 建议
-
优先使用不可变容器:在不需要修改数据时,优先使用元组、冻结集合和字符串,它们更安全、更高效。
-
选择合适的字典变体:
- 需要默认值时使用
defaultdict - 需要严格顺序时使用
OrderedDict - 需要计数时使用
Counter
- 需要默认值时使用
-
避免在循环中修改列表:在循环中修改列表可能导致意外行为和性能问题。如果需要过滤或转换列表,使用列表推导式。
-
使用集合进行成员检查:如果需要频繁检查元素是否存在,使用集合而不是列表,集合的成员检查是O(1)时间复杂度。
-
使用deque实现队列和栈:deque比列表更适合实现队列和栈,特别是当需要在两端进行操作时。
-
使用NamedTuple提高代码可读性:当需要表示简单的数据结构时,使用NamedTuple而不是普通元组,它可以通过名称访问元素,提高代码可读性。
-
注意字典的内存开销:字典的内存开销较大,如果只需要存储键而不需要值,使用集合。
六、注意事项
-
列表的浅拷贝问题:
pythona = [1, 2, [3, 4]] b = a.copy() # 浅拷贝 b[2].append(5) print(a) # [1, 2, [3, 4, 5]],a也被修改了解决方法:使用
copy.deepcopy()进行深拷贝。 -
字典的键必须可哈希 :
列表、字典和集合等不可哈希对象不能作为字典的键。如果需要使用复杂对象作为键,可以使用元组或自定义类并实现
__hash__和__eq__方法。 -
集合的元素必须可哈希 :
与字典的键类似,集合的元素也必须可哈希。
-
字符串的不可变性 :
字符串是不可变的,所有修改字符串的操作都会返回新的字符串。频繁修改字符串会导致性能问题,此时可以使用
bytearray或io.StringIO。 -
空集合的创建 :
{}创建的是空字典,而不是空集合。创建空集合应该使用set()。 -
元组的单元素问题 :
单元素元组必须在元素后面加逗号,否则会被解释为普通表达式。
pythont = (5) # 这是整数5,不是元组 t = (5,) # 这才是单元素元组
七、总结
- 序列类型适合存储有序数据,其中列表用于可变数据,元组用于不可变数据
- 映射类型适合存储键值对数据,提供快速的查找、插入和删除操作
- 集合类型适合存储唯一元素,支持高效的成员检查和数学集合运算
- collections模块提供了多种增强型容器,解决了基础容器的一些局限性