写 Python 代码,80% 的时间在操作数据结构。本文不讲抽象理论,而是告诉你每种结构什么时候用、怎么用最快、有什么坑。
目录
1. 总览:选择正确的数据结构
Python 内置了丰富的数据结构,每种都有其最佳使用场景:
需要有序序列?
├── 需要修改 → list(动态数组)
└── 不需要修改 → tuple(不可变,更快)
需要键值查找?
└── dict(哈希表,O(1) 查找)
需要成员检测/去重?
└── set(哈希集合)
需要计数/频率统计?
└── collections.Counter
需要双端操作?
└── collections.deque
需要分组/默认值?
└── collections.defaultdict
2. list:动态数组的利与弊
2.1 基本操作与时间复杂度
# 创建
lst = [1, 2, 3, 4, 5]
lst = list(range(1000))
# 访问(O(1))
print(lst[0]) # 第一个
print(lst[-1]) # 最后一个(Pythonic!)
print(lst[2:5]) # 切片:[3, 4, 5]
# 修改(O(1))
lst[0] = 100
# 末尾添加(均摊 O(1))
lst.append(6)
# 插入(O(n))—— 尽量避免在头部/中间插入
lst.insert(0, 0) # 所有元素后移!
# 删除
lst.pop() # 末尾弹出,O(1)
lst.pop(0) # 头部弹出,O(n)!
lst.remove(3) # 按值删除,O(n)
# 查找
try:
idx = lst.index(42) # O(n)
except ValueError:
idx = -1
# 存在性检测
if 42 in lst: # O(n)
pass
2.2 列表推导式:Pythonic 的核心
# ❌ 非 Pythonic
result = []
for x in range(10):
if x % 2 == 0:
result.append(x ** 2)
# ✅ Pythonic
result = [x ** 2 for x in range(10) if x % 2 == 0]
# 字典推导式
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 集合推导式
unique_squares = {x**2 for x in range(100)} # 自动去重
2.3 常见陷阱
# ❌ 陷阱 1:可变默认参数
def foo(x, items=[]):
items.append(x)
return items
foo(1) # [1]
foo(2) # [1, 2] —— 同一个列表!
# ✅ 正确
from typing import List
def foo(x, items: List[int] = None):
if items is None:
items = []
items.append(x)
return items
# ❌ 陷阱 2:list 复制
a = [1, 2, [3, 4]]
b = a[:] # 浅拷贝!b[2] is a[2]
c = a.copy() # 同样是浅拷贝
import copy
d = copy.deepcopy(a) # 深拷贝
# ❌ 陷阱 3:循环中修改列表
for item in lst:
if item < 0:
lst.remove(item) # ❌ 修改正在迭代的列表
# ✅ 正确:用列表推导式或倒序删除
lst = [x for x in lst if x >= 0]
# 或
for i in range(len(lst) - 1, -1, -1):
if lst[i] < 0:
lst.pop(i)
2.4 性能技巧
# 预分配空间(大数据量时有用)
lst = [None] * 10000
for i in range(10000):
lst[i] = i * 2
# 使用 array 模块存储同类型数据(更省内存)
from array import array
numbers = array('i', [1, 2, 3, 4, 5]) # 'i' = signed int
# 内存占用对比
import sys
py_list = [0] * 1000
arr = array('i', [0] * 1000)
print(f"list: {sys.getsizeof(py_list)} bytes")
print(f"array: {sys.getsizeof(arr)} bytes")
3. tuple:不可变序列的妙用
3.1 为什么需要 tuple
# 1. 作为 dict 的 key(需要不可变)
locations = {
(39.9, 116.4): "北京",
(31.2, 121.5): "上海",
}
# 2. 解构赋值(比 list 更清晰)
point = (3, 4)
x, y = point
# 3. 函数多返回值
def get_min_max(nums):
return min(nums), max(nums)
minimum, maximum = get_min_max([3, 1, 4, 1, 5])
# 4. 比 list 更省内存、更快
import sys
lst = [1, 2, 3]
t = (1, 2, 3)
print(sys.getsizeof(lst)) # 88 bytes
print(sys.getsizeof(t)) # 72 bytes
3.2 namedtuple:给 tuple 加名字
from collections import namedtuple
# 创建具名元组类
Person = namedtuple('Person', ['name', 'age', 'email'])
# 使用
alice = Person(name='Alice', age=25, email='alice@example.com')
print(alice.name) # Alice(比 alice[0] 清晰多了)
# 可以替换字段
bob = alice._replace(name='Bob', age=30)
# 从 dict 创建
data = {'name': 'Charlie', 'age': 35, 'email': 'charlie@example.com'}
charlie = Person(**data)
Python 3.7+ 推荐用 dataclass 替代 namedtuple:
from dataclasses import dataclass
@dataclass(frozen=True) # frozen=True 使其不可变
class Person:
name: str
age: int
email: str
alice = Person(name='Alice', age=25, email='alice@example.com')
print(alice.name)
4. dict:哈希表的工程实践
4.1 核心操作
# 创建
d = {'a': 1, 'b': 2}
d = dict(a=1, b=2)
d = {x: x**2 for x in range(5)} # 字典推导式
# 访问(O(1))
print(d['a']) # 1
d['c'] = 3 # 添加/修改
# 安全访问
print(d.get('z', 0)) # 0(不存在时返回默认值)
# 存在性检测
if 'a' in d: # O(1)
pass
# 删除
del d['a']
popped = d.pop('b') # 删除并返回值
# 遍历
for key in d: # 等价于 for key in d.keys():
print(key)
for key, value in d.items():
print(f"{key}: {value}")
for value in d.values():
print(value)
4.2 dict 的插入顺序(Python 3.7+)
# Python 3.7+ 起,dict 保留插入顺序(语言规范保证)
d = {}
d['first'] = 1
d['second'] = 2
d['third'] = 3
print(list(d.keys())) # ['first', 'second', 'third']
4.3 merge 操作(Python 3.9+)
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
# | 合并(d2 覆盖 d1 的重复键)
merged = d1 | d2 # {'a': 1, 'b': 3, 'c': 4}
# |= 就地更新
d1 |= d2 # d1 变为 {'a': 1, 'b': 3, 'c': 4}
4.4 高级技巧
# 1. 用 setdefault 统计
char_count = {}
for char in "hello world":
char_count.setdefault(char, 0)
char_count[char] += 1
# 2. 用 collections.Counter 更简单
from collections import Counter
char_count = Counter("hello world")
# 3. 多级字典(需要默认值)
# ❌ 手动检查
if 'users' not in data:
data['users'] = {}
if 'alice' not in data['users']:
data['users']['alice'] = []
data['users']['alice'].append('action1')
# ✅ 用 defaultdict
from collections import defaultdict
data = defaultdict(lambda: defaultdict(list))
data['users']['alice'].append('action1') # 自动创建中间层级
5. set:集合运算与去重
5.1 基本操作
# 创建
s = {1, 2, 3, 3, 3} # {1, 2, 3} —— 自动去重
s = set([1, 2, 2, 3]) # {1, 2, 3}
# 添加/删除
s.add(4)
s.remove(2) # KeyError if not found
s.discard(999) # 不报错
# 存在性检测(O(1))
if 3 in s:
pass
5.2 集合运算(超级实用)
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# 并集
print(a | b) # {1, 2, 3, 4, 5, 6}
print(a.union(b))
# 交集
print(a & b) # {3, 4}
print(a.intersection(b))
# 差集
print(a - b) # {1, 2}(在 a 中但不在 b 中)
print(a.difference(b))
# 对称差集(只在一边出现的)
print(a ^ b) # {1, 2, 5, 6}
print(a.symmetric_difference(b))
# 子集/超集
print(a <= b) # False(a 是 b 的子集?)
print({1, 2} <= a) # True
5.3 实战:列表去重并保持顺序
def unique_ordered(items):
"""去重并保持原始顺序"""
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
# Python 3.7+(dict 有序)
def unique_ordered_v2(items):
return list(dict.fromkeys(items))
# 测试
data = [3, 1, 4, 1, 5, 9, 2, 6, 5]
print(unique_ordered(data)) # [3, 1, 4, 5, 9, 2, 6]
print(unique_ordered_v2(data)) # [3, 1, 4, 5, 9, 2, 6]
6. collections 模块进阶
collections 是 Python 最实用的标准库模块之一,它提供了数据结构的专业版。
6.1 Counter:频率统计
from collections import Counter
# 统计字符频率
text = "hello world"
count = Counter(text)
print(count) # Counter({'l': 3, 'o': 2, 'h': 1, ...})
print(count.most_common(3)) # [('l', 3), ('o', 2), ('h', 1)]
# 统计单词
words = "the quick brown fox jumps over the lazy dog".split()
word_count = Counter(words)
print(word_count.most_common(2)) # [('the', 2), ('quick', 1)]
# 集合运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2, c=3)
print(c1 + c2) # Counter({'a': 4, 'b': 3, 'c': 3})
print(c1 - c2) # Counter({'a': 2}) —— 只保留正数
6.2 deque:双端队列
from collections import deque
# deque 两端操作都是 O(1),list 头部操作是 O(n)
d = deque([1, 2, 3, 4, 5])
# 右端操作(和 list 一样)
d.append(6) # deque([1, 2, 3, 4, 5, 6])
d.pop() # 6
# 左端操作(O(1)!)
d.appendleft(0) # deque([0, 1, 2, 3, 4, 5])
d.popleft() # 0
# 旋转
d.rotate(1) # 向右旋转 1 位
d.rotate(-2) # 向左旋转 2 位
# 定长队列(自动丢弃旧元素)
cache = deque(maxlen=3)
cache.append(1)
cache.append(2)
cache.append(3)
cache.append(4) # 自动丢弃 1
cache # deque([2, 3, 4], maxlen=3)
**适用场景:**实现 LRU Cache、滑动窗口、BFS 队列。
6.3 defaultdict:自动初始化的字典
from collections import defaultdict
# 场景 1:按首字母分组
words = ['apple', 'bat', 'bar', 'atom', 'book']
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
print(dict(by_letter))
# {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}
# 场景 2:计数(比 Counter 更灵活)
counts = defaultdict(int)
for word in words:
counts[word[0]] += 1
# 场景 3:树形结构
Tree = lambda: defaultdict(Tree)
tree = Tree()
tree['users']['alice']['age'] = 25
6.4 OrderedDict:有序字典(现在基本不需要了)
from collections import OrderedDict
# Python 3.7+ 的普通 dict 已经有序,OrderedDict 主要用于:
# 1. 明确表达"顺序重要"的意图
# 2. 使用 move_to_end 等方法
d = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
d.move_to_end('a') # 把 'a' 移到最后
6.5 ChainMap:字典查找链
from collections import ChainMap
# 合并多个字典查找(不复制数据)
defaults = {'theme': 'dark', 'language': 'en'}
user_prefs = {'theme': 'light'}
config = ChainMap(user_prefs, defaults)
print(config['theme']) # 'light'(从第一个 dict 找到)
print(config['language']) # 'en'(从第二个 dict 找到)
# 用于参数解析:命令行 > 环境变量 > 配置文件
cmd_args = {'verbose': True}
env_vars = {'output_dir': '/tmp'}
config_file = {'output_dir': './output'}
settings = ChainMap(cmd_args, env_vars, config_file)
print(settings['verbose']) # True(命令行)
print(settings['output_dir']) # /tmp(环境变量覆盖配置文件)
7. 时间复杂度速查表
list
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
访问 lst[i] | O(1) | |
末尾添加 append | O(1) 均摊 | 偶尔需要扩容 |
末尾弹出 pop() | O(1) | |
插入 insert(i, x) | O(n) | i 处元素后移 |
删除 pop(i) | O(n) | i 处元素前移 |
查找 index(x) | O(n) | |
x in lst | O(n) | |
切片 lst[a:b] | O(k) | k = b-a |
排序 sort() | O(n log n) | Timsort |
dict / set
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 访问/插入/删除 | O(1) 均摊 | 哈希冲突时退化 |
key in d | O(1) | |
| 遍历 | O(n) | |
keys() / values() | O(1) | 返回视图,不复制 |
deque
| 操作 | 时间复杂度 |
|---|---|
| 两端 append/pop | O(1) |
| 访问中间元素 | O(n) |
rotate() | O(k) |
8. 实战场景选型决策
场景 1:处理大规模日志,统计 IP 频率
from collections import Counter
import re
# 日志格式:192.168.1.1 - - [12/Aug/2024 ...]
IP_PATTERN = re.compile(r'^(\d+\.\d+\.\d+\.\d+)')
def analyze_ips(logfile):
ip_counter = Counter()
with open(logfile) as f:
for line in f:
m = IP_PATTERN.match(line)
if m:
ip_counter[m.group(1)] += 1
return ip_counter.most_common(10)
选型理由:Counter 专门用于频率统计,代码简洁、性能优秀。
场景 2:实现 LRU Cache(最近最少使用缓存)
from collections import OrderedDict
class LRUCache:
"""使用 OrderedDict 实现 LRU Cache"""
def __init__(self, capacity: int):
self.capacity = capacity
self.cache: OrderedDict[int, int] = OrderedDict()
def get(self, key: int) -> int:
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
# Python 3.2+ 更简单的实现
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
场景 3:解析嵌套 JSON 配置
from collections import defaultdict
import json
def nested_dict():
return defaultdict(nested_dict)
def load_config(path):
with open(path) as f:
data = json.load(f)
config = nested_dict()
def merge(d, keys=()):
for k, v in d.items():
new_keys = keys + (k,)
if isinstance(v, dict):
merge(v, new_keys)
else:
target = config
for key in keys:
target = target[key]
target[k] = v
merge(data)
return config
场景 4:滑动窗口最大值
from collections import deque
from typing import List
def max_sliding_window(nums: List[int], k: int) -> List[int]:
"""O(n) 时间求滑动窗口最大值"""
result = []
dq = deque() # 存索引,保持单调递减
for i, num in enumerate(nums):
# 移除窗口外的元素
if dq and dq[0] < i - k + 1:
dq.popleft()
# 保持单调递减:移除所有小于当前元素的
while dq and nums[dq[-1]] < num:
dq.pop()
dq.append(i)
# 窗口填满后开始输出
if i >= k - 1:
result.append(nums[dq[0]])
return result
# 测试
nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
print(max_sliding_window(nums, k)) # [3, 3, 5, 5, 6, 7]
延伸阅读
- Python 极简入门教程 —— 系统学习 Python 基础
- Python 内存管理、垃圾回收与性能调优 —— dict 扩容机制与内存优化
- Python 类型系统与 Pydantic V2 —— 类型注解与数据结构结合
- Python 数据科学与 AI 实战 —— Pandas 数据结构
数据结构是程序设计的基石。在 Python 中,选择
list还是deque,用dict还是Counter,往往比算法优化更能提升性能。记住一个原则:先选对结构,再优化算法。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。