大家好,我是正在实战各种 AI 项目的程序员晚枫。

🎬 开篇:同样的数据处理,为什么别人更快更省内存?

你有没有遇到过这样的场景?

处理一个 100 万行的 CSV 文件:

  • 同事 A 的代码:几秒钟跑完,内存占用不到 100MB
  • 你的代码:跑了 2 分钟,内存占用飙到 2GB

为什么差距这么大?

答案就在今天要讲的内容:列表推导式生成器表达式、以及 Python 容器的底层原理。

一个真实的案例

2024 年,我帮一个金融公司优化数据处理脚本。

原代码用传统的 for 循环处理每日报表,100 万条数据需要 3 分钟。

我用生成器表达式 + 批量处理优化后,同样的数据只需 15 秒,内存占用从 1.8GB 降到 200MB。

这就是 Python 容器使用的差距。


🚀 列表推导式:更快更简洁的数据处理

什么是列表推导式?

列表推导式(List Comprehension)是 Python 的一种语法糖,让你用一行代码完成列表的创建和转换。

1
2
3
4
5
6
7
8
9
# ❌ 传统写法:5 行代码
squares = []
for x in range(10):
squares.append(x ** 2)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# ✅ 列表推导式:1 行代码
squares = [x ** 2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

列表推导式的语法

1
2
3
4
5
6
7
8
9
10
11
# 基本语法
[expression for item in iterable]

# 带条件筛选
[expression for item in iterable if condition]

# 带条件表达式(三元运算符)
[expression_if_true if condition else expression_if_false for item in iterable]

# 多重循环
[expression for item1 in iterable1 for item2 in iterable2]

基础用法详解

1. 简单转换

1
2
3
4
5
6
7
8
9
10
11
12
13
# 数字平方
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 字符串转大写
words = ['hello', 'world', 'python']
upper_words = [word.upper() for word in words]
# ['HELLO', 'WORLD', 'PYTHON']

# 提取属性
users = [{'name': '张三', 'age': 25}, {'name': '李四', 'age': 30}]
names = [user['name'] for user in users]
# ['张三', '李四']

2. 条件筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 筛选偶数
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# 筛选非空字符串
texts = ['hello', '', 'world', None, 'python', '']
valid_texts = [s for s in texts if s]
# ['hello', 'world', 'python']

# 筛选符合条件的字典
products = [
{'name': 'iPhone', 'price': 5999},
{'name': '小米', 'price': 1999},
{'name': '华为', 'price': 4999},
]
expensive = [p for p in products if p['price'] > 3000]
# [{'name': 'iPhone', 'price': 5999}, {'name': '华为', 'price': 4999}]

3. 条件表达式(三元运算符)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 根据条件返回不同值
numbers = range(10)
labels = ['偶数' if x % 2 == 0 else '奇数' for x in numbers]
# ['偶数', '奇数', '偶数', '奇数', '偶数', '奇数', '偶数', '奇数', '偶数', '奇数']

# 处理负数
values = [-3, -1, 0, 2, 4, -5]
abs_values = [x if x >= 0 else -x for x in values]
# [3, 1, 0, 2, 4, 5]

# 默认值处理
names = ['Alice', '', 'Bob', None, 'Charlie']
valid_names = [name if name else '匿名' for name in names]
# ['Alice', '匿名', 'Bob', '匿名', 'Charlie']

4. 多重循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 笛卡尔积
colors = ['红', '蓝', '绿']
sizes = ['S', 'M', 'L']
combinations = [(color, size) for color in colors for size in sizes]
# [('红', 'S'), ('红', 'M'), ('红', 'L'), ('蓝', 'S'), ('蓝', 'M'), ('蓝', 'L'), ('绿', 'S'), ('绿', 'M'), ('绿', 'L')]

# 展平二维列表
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 带条件的双重循环
pairs = [(x, y) for x in range(5) for y in range(5) if x < y]
# [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

列表推导式的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 嵌套列表推导式(注意可读性)
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# 等价于
matrix = []
for i in range(1, 4):
row = []
for j in range(1, 4):
row.append(i * j)
matrix.append(row)

# 转置矩阵
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

性能对比

列表推导式比传统循环快 1.5-2 倍,原因:

  1. 避免 append 方法调用:每次 append() 都是函数调用开销
  2. 内部 C 优化:列表推导式在 Python 内部使用优化的 C 代码
  3. 减少字节码:更少的字节码指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import timeit

# 测试列表推导式
list_comp = '[x ** 2 for x in range(1000)]'
list_comp_time = timeit.timeit(list_comp, number=10000)

# 测试普通循环
loop = '''
result = []
for x in range(1000):
result.append(x ** 2)
'''
loop_time = timeit.timeit(loop, number=10000)

# 测试 map + lambda
map_time = timeit.timeit('list(map(lambda x: x ** 2, range(1000)))', number=10000)

print(f"列表推导式: {list_comp_time:.4f}s")
print(f"普通循环: {loop_time:.4f}s")
print(f"map+lambda: {map_time:.4f}s")
print(f"推导式比循环快: {(loop_time / list_comp_time - 1) * 100:.1f}%")

输出示例:

1
2
3
4
列表推导式: 0.3214s
普通循环: 0.5623s
map+lambda: 0.4891s
推导式比循环快: 75.0%

何时使用列表推导式?

✅ 适合使用:

  • 逻辑简单,一行能写完
  • 只做转换和筛选
  • 代码可读性高

❌ 不适合使用:

  • 需要复杂的处理逻辑
  • 需要 try-except 处理异常
  • 需要多个步骤的中间变量
1
2
3
4
5
6
7
8
9
10
11
12
13
# ✅ 适合:简单转换
prices = [99.9, 199.9, 299.9]
int_prices = [int(p) for p in prices]

# ❌ 不适合:复杂逻辑(用普通循环更清晰)
results = []
for item in data:
try:
value = complex_calculation(item)
if validate(value):
results.append(process(value))
except ValueError:
results.append(None)

💡 生成器表达式:惰性求值的威力

什么是生成器表达式?

生成器表达式(Generator Expression)是列表推导式的"惰性版本":

  • 不立即计算所有值
  • 只在需要时才计算
  • 节省内存,特别是处理大数据时
1
2
3
4
5
# 列表推导式:立即计算,占用内存
squares_list = [x ** 2 for x in range(1000000)] # 约 8MB 内存

# 生成器表达式:惰性计算,几乎不占内存
squares_gen = (x ** 2 for x in range(1000000)) # 几乎 0 内存

语法对比

1
2
3
4
5
6
7
8
9
10
11
# 列表推导式 - 方括号 []
squares_list = [x ** 2 for x in range(10)]

# 生成器表达式 - 圆括号 ()
squares_gen = (x ** 2 for x in range(10))

# 字典推导式 - 花括号 {}
squares_dict = {x: x ** 2 for x in range(10)}

# 集合推导式 - 花括号 {}
squares_set = {x ** 2 for x in range(10)}

内存占用对比

1
2
3
4
5
6
7
8
9
import sys

# 列表:存储所有元素
big_list = [x for x in range(1000000)]
print(f"列表大小: {sys.getsizeof(big_list) / 1024 / 1024:.2f} MB") # 约 8 MB

# 生成器:只存储算法
big_gen = (x for x in range(1000000))
print(f"生成器大小: {sys.getsizeof(big_gen)} bytes") # 约 112 bytes

使用场景详解

1. 求和、求最大最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ❌ 不好的写法:先创建列表再求和
total = sum([x ** 2 for x in range(1000000)])

# ✅ 好的写法:直接用生成器表达式
total = sum(x ** 2 for x in range(1000000))

# 性能对比
import timeit

list_sum = timeit.timeit('sum([x**2 for x in range(10000)])', number=1000)
gen_sum = timeit.timeit('sum(x**2 for x in range(10000))', number=1000)

print(f"列表求和: {list_sum:.4f}s")
print(f"生成器求和: {gen_sum:.4f}s")
# 生成器版本略慢(需要逐个生成),但省内存

2. 文件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ❌ 不好的写法:一次读取所有行
with open('large_file.txt') as f:
lines = [line.strip() for line in f]
# 如果文件很大,内存爆炸
for line in lines:
process(line)

# ✅ 好的写法:生成器逐行处理
with open('large_file.txt') as f:
for line in (line.strip() for line in f):
process(line)
# 只有一行在内存中

# ✅ 更好的写法:直接迭代文件对象
with open('large_file.txt') as f:
for line in f:
process(line.strip())

3. 管道式处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 处理日志文件:提取错误信息
def process_logs(filename):
with open(filename) as f:
# 管道式处理链
errors = (
line for line in f
if 'ERROR' in line
)
timestamps = (
line.split()[0] for line in errors
)
return list(timestamps)

# 等价于
def process_logs_verbose(filename):
with open(filename) as f:
result = []
for line in f:
if 'ERROR' in line:
timestamp = line.split()[0]
result.append(timestamp)
return result

生成器表达式 vs 列表推导式

特性列表推导式生成器表达式
语法[...](...)
内存占用高(存储所有元素)低(只存储算法)
计算时机立即计算惰性计算
可迭代次数无限次只能一次
支持索引支持不支持
支持切片支持不支持

选择建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 场景1:需要多次遍历 → 用列表
data = [x ** 2 for x in range(100)]
print(sum(data)) # 第一次遍历
print(max(data)) # 第二次遍历
print(min(data)) # 第三次遍历

# 场景2:只遍历一次 → 用生成器
total = sum(x ** 2 for x in range(1000000)) # 只遍历一次

# 场景3:需要索引或切片 → 用列表
data = [x ** 2 for x in range(100)]
print(data[10]) # 索引
print(data[10:20]) # 切片

# 场景4:数据量大 → 用生成器
big_data = (process(x) for x in huge_dataset)

📦 元组:不只是不可变的列表

元组的两种用途

Python 元组有两个截然不同的用途:

  1. 不可变列表:存储不能修改的数据
  2. 记录:存储不同类型的数据项(类似数据库行)
1
2
3
4
5
6
7
# 用途1:不可变列表(相同类型)
coordinates = (10, 20, 30)
rgb = (255, 128, 0)

# 用途2:记录(不同类型)
person = ('张三', 25, 'engineer', 'zhang@example.com')
# (name, age, job, email)

元组拆包

元组拆包(Unpacking)是 Python 的强大特性:

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
26
27
28
# 基础拆包
point = (3, 4)
x, y = point
print(x, y) # 3 4

# 交换变量
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1

# 拆包到多个变量
person = ('张三', 25, 'engineer')
name, age, job = person
print(f"{name}{age} 岁的 {job}")

# 使用 * 捕获剩余元素
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]

head, *middle, tail = [1, 2, 3, 4, 5]
print(head) # 1
print(middle) # [2, 3, 4]
print(tail) # 5

# 忽略某些值
name, _, email = ('张三', 25, 'zhang@example.com')
print(name, email) # 张三 zhang@example.com

嵌套拆包

1
2
3
4
5
6
7
8
9
10
11
# 嵌套元组拆包
person = ('张三', (25, 'engineer'), ['Python', 'Java'])
name, (age, job), skills = person
print(name) # 张三
print(age) # 25
print(job) # engineer
print(skills) # ['Python', 'Java']

# 实际应用:遍历带索引的项
for index, (name, score) in enumerate([('Alice', 95), ('Bob', 87)]):
print(f"{index}: {name} - {score}")

命名元组

namedtuple 给元组字段命名,让代码更可读:

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
26
27
28
29
30
31
from collections import namedtuple

# 定义命名元组
Point = namedtuple('Point', ['x', 'y'])
Person = namedtuple('Person', 'name age job')

# 创建实例
p = Point(3, 4)
print(p.x, p.y) # 3 4 - 可以用名字访问
print(p[0], p[1]) # 3 4 - 也可以用索引访问

# 命名元组的方法
print(p._fields) # ('x', 'y') - 字段名
print(p._asdict()) # {'x': 3, 'y': 4} - 转字典
p2 = p._replace(x=5) # 创建新实例
print(p2) # Point(x=5, y=4)

# 实际应用:表示记录
Employee = namedtuple('Employee', 'id name department salary')
emp = Employee(1, '张三', '技术部', 15000)
print(f"员工 {emp.name} 薪资: {emp.salary}")

# 命名元组 vs 普通元组
# ❌ 普通元组:需要记住位置
record = ('张三', 25, 'engineer')
print(f"姓名: {record[0]}, 年龄: {record[1]}")

# ✅ 命名元组:字段名清晰
Person = namedtuple('Person', 'name age job')
record = Person('张三', 25, 'engineer')
print(f"姓名: {record.name}, 年龄: {record.age}")

命名元组进阶用法

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
26
27
28
29
30
31
32
33
34
from collections import namedtuple

# 1. 默认值(Python 3.7+)
Person = namedtuple('Person', 'name age', defaults=['未知', 0])
p = Person('张三')
print(p) # Person(name='张三', age=0)

# 2. 类型提示增强
from typing import NamedTuple

class Point(NamedTuple):
x: float
y: float

def distance(self):
return (self.x ** 2 + self.y ** 2) ** 0.5

p = Point(3, 4)
print(p.distance()) # 5.0

# 3. 可选字段
from dataclasses import dataclass

@dataclass
class Person:
name: str
age: int = 0
job: str = '未就业'

def __str__(self):
return f"{self.name}({self.age}岁, {self.job})"

p = Person('张三', job='工程师')
print(p) # 张三(0岁, 工程师)

🗂️ 字典:Python 最强大的数据结构

字典推导式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 基础用法:创建字典
words = ['apple', 'banana', 'cherry']
word_lengths = {word: len(word) for word in words}
print(word_lengths) # {'apple': 5, 'banana': 6, 'cherry': 6}

# 从两个列表创建字典
keys = ['a', 'b', 'c']
values = [1, 2, 3]
d = {k: v for k, v in zip(keys, values)}
print(d) # {'a': 1, 'b': 2, 'c': 3}

# 条件筛选
scores = {'Alice': 95, 'Bob': 67, 'Charlie': 82, 'David': 55}
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed) # {'Alice': 95, 'Bob': 67, 'Charlie': 82}

# 值转换
prices = {'apple': 3.5, 'banana': 2.8, 'cherry': 4.2}
formatted = {fruit: f'¥{price:.2f}' for fruit, price in prices.items()}
print(formatted) # {'apple': '¥3.50', 'banana': '¥2.80', 'cherry': '¥4.20'}

字典合并的多种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
dict3 = {'b': 20, 'e': 5} # 注意 b 有冲突

# 方式1:update() 方法(原地修改)
result = dict1.copy()
result.update(dict2)
print(result) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 方式2:** 解包(Python 3.5+)
result = {**dict1, **dict2}
print(result) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 方式3:| 运算符(Python 3.9+)
result = dict1 | dict2
print(result) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 冲突处理:后面的覆盖前面的
result = dict1 | dict3
print(result) # {'a': 1, 'b': 20, 'e': 5}

# |= 原地合并(Python 3.9+)
dict1 |= dict2
print(dict1) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

字典的常用技巧

1. setdefault 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ❌ 传统写法
word_counts = {}
for word in ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']:
if word not in word_counts:
word_counts[word] = 0
word_counts[word] += 1

# ✅ setdefault 写法
word_counts = {}
for word in ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']:
word_counts.setdefault(word, 0)
word_counts[word] += 1

# ✅✅ 更好的方式:用 defaultdict 或 Counter
from collections import defaultdict, Counter

word_counts = defaultdict(int)
for word in ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']:
word_counts[word] += 1

# 或者直接用 Counter
word_counts = Counter(['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'])
print(word_counts) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})

2. defaultdict 高级用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from collections import defaultdict

# 分组
words = ['apple', 'apricot', 'banana', 'blueberry', 'cherry']
by_first_letter = defaultdict(list)
for word in words:
by_first_letter[word[0]].append(word)
print(dict(by_first_letter))
# {'a': ['apple', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# 多值字典
from collections import defaultdict

multi_dict = defaultdict(list)
multi_dict['fruits'].extend(['apple', 'banana'])
multi_dict['fruits'].append('cherry')
print(dict(multi_dict)) # {'fruits': ['apple', 'banana', 'cherry']}

# 自定义默认值
def default_person():
return {'name': '未知', 'age': 0}

people = defaultdict(default_person)
print(people['nonexistent']) # {'name': '未知', 'age': 0}

3. 字典视图

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
26
27
d = {'a': 1, 'b': 2, 'c': 3}

# keys(), values(), items() 返回的是视图,不是列表
keys = d.keys()
values = d.values()
items = d.items()

# 视图是动态的
d['d'] = 4
print(list(keys)) # ['a', 'b', 'c', 'd'] - 自动更新
print(list(values)) # [1, 2, 3, 4]

# 集合操作
d1 = {'a': 1, 'b': 2}
d2 = {'b': 20, 'c': 3}

# 交集
common_keys = d1.keys() & d2.keys()
print(common_keys) # {'b'}

# 并集
all_keys = d1.keys() | d2.keys()
print(all_keys) # {'a', 'b', 'c'}

# 差集
diff_keys = d1.keys() - d2.keys()
print(diff_keys) # {'a'}

字典的内存优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sys

# 普通字典
d = {}
for i in range(1000):
d[i] = i * 2

print(f"普通字典: {sys.getsizeof(d)} bytes") # 约 36KB

# __slots__ 优化(适用于对象)
class Point:
__slots__ = ['x', 'y']

def __init__(self, x, y):
self.x = x
self.y = y

points = [Point(i, i*2) for i in range(1000)]
print(f"__slots__对象: {sys.getsizeof(points[0])} bytes")

🔪 切片:Python 最优雅的数据提取方式

切片基础

1
2
3
4
5
6
7
8
9
10
11
# 基本语法:seq[start:stop:step]
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nums[2:5]) # [2, 3, 4] - 索引 2,3,4
print(nums[:3]) # [0, 1, 2] - 前 3 个
print(nums[7:]) # [7, 8, 9] - 从索引 7 到末尾
print(nums[-3:]) # [7, 8, 9] - 最后 3 个
print(nums[:-3]) # [0, 1, 2, 3, 4, 5, 6] - 排除最后 3 个
print(nums[::2]) # [0, 2, 4, 6, 8] - 步长 2
print(nums[1::2]) # [1, 3, 5, 7, 9] - 奇数索引
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - 反转

切片赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 切片可以修改原序列
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 替换切片
nums[2:5] = [20, 30, 40]
print(nums) # [0, 1, 20, 30, 40, 5, 6, 7, 8, 9]

# 删除切片
nums[2:5] = []
print(nums) # [0, 1, 5, 6, 7, 8, 9]

# 插入元素
nums[2:2] = [2, 3, 4]
print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 步长赋值
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums[::2] = [0, 0, 0, 0, 0]
print(nums) # [0, 1, 0, 3, 0, 5, 0, 7, 0, 9]

多维切片

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
26
27
28
import numpy as np

# NumPy 支持多维切片
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr[:2, 1:]) # [[2, 3], [5, 6]]

# 自定义多维切片
class Matrix:
def __init__(self, data):
self.data = data

def __getitem__(self, key):
if isinstance(key, tuple):
row, col = key
if isinstance(row, slice) and isinstance(col, slice):
return [r[col] for r in self.data[row]]
elif isinstance(row, slice):
return [r[col] for r in self.data[row]]
elif isinstance(col, slice):
return self.data[row][col]
else:
return self.data[row][col]
return self.data[key]

m = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(m[0, 1]) # 2
print(m[0, :]) # [1, 2, 3]
print(m[:, 0]) # [1, 4, 7]

slice 对象

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
# slice 对象可以命名切片
items = list(range(10))

# 创建切片对象
first_three = slice(0, 3)
last_three = slice(-3, None)
every_other = slice(None, None, 2)

print(items[first_three]) # [0, 1, 2]
print(items[last_three]) # [7, 8, 9]
print(items[every_other]) # [0, 2, 4, 6, 8]

# 实际应用:处理固定格式数据
records = [
"2024-01-15 Alice 95",
"2024-01-16 Bob 87",
"2024-01-17 Carol 92",
]

date_slice = slice(0, 10)
name_slice = slice(11, 17)
score_slice = slice(18, 20)

for record in records:
print(f"日期: {record[date_slice]}, 姓名: {record[name_slice]}, 分数: {record[score_slice]}")

⚠️ 避坑指南

陷阱 1:在推导式中修改外部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ❌ 错误:推导式中赋值不会影响外部变量
x = 10
result = [x := x + i for i in range(5)] # Python 3.8+
# 注意:这是新语法,旧版本不支持

# ✅ 正确:使用函数
def accumulate(start, values):
result = []
current = start
for v in values:
current += v
result.append(current)
return result

result = accumulate(10, range(5))
print(result) # [10, 11, 13, 16, 20]

陷阱 2:生成器只能遍历一次

1
2
3
4
5
6
7
8
9
10
# ❌ 错误:生成器遍历后"耗尽"
gen = (x ** 2 for x in range(5))
print(list(gen)) # [0, 1, 4, 9, 16]
print(list(gen)) # [] - 第二次为空!

# ✅ 正确:需要多次使用时转换为列表
gen = (x ** 2 for x in range(5))
lst = list(gen)
print(lst) # [0, 1, 4, 9, 16]
print(lst) # [0, 1, 4, 9, 16]

陷阱 3:切片创建的是新对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 切片总是创建新对象
a = [1, 2, 3, 4, 5]
b = a[:]
print(a is b) # False - 不同对象

# 但内部元素是共享的
a = [[1, 2], [3, 4]]
b = a[:]
b[0][0] = 999
print(a) # [[999, 2], [3, 4]] - 内部列表被修改!

# ✅ 深拷贝
import copy
b = copy.deepcopy(a)

陷阱 4:字典迭代顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Python 3.7+ 字典保持插入顺序
d = {}
d['a'] = 1
d['c'] = 3
d['b'] = 2
print(list(d.keys())) # ['a', 'c', 'b'] - 按插入顺序

# 但不要依赖顺序做逻辑
# ❌ 错误假设:字典按键排序
d = {'c': 3, 'a': 1, 'b': 2}
print(list(d.keys())) # ['c', 'a', 'b'] - 插入顺序,不是字母顺序

# ✅ 如果需要排序
sorted_keys = sorted(d.keys())
print(sorted_keys) # ['a', 'b', 'c']

🎯 实战案例:处理大型 CSV 文件

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import csv
from collections import defaultdict

# 场景:处理 1GB 的销售数据 CSV
# 需求:按产品类别统计销售额

def process_large_csv(filename):
"""
处理大型 CSV 文件,按类别统计销售额
使用生成器避免内存溢出
"""
# 使用 defaultdict 累加
category_sales = defaultdict(float)

with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)

# 使用生成器表达式过滤和处理
valid_rows = (
row for row in reader
if row['amount'] and float(row['amount']) > 0
)

for row in valid_rows:
category = row['category']
amount = float(row['amount'])
category_sales[category] += amount

return dict(category_sales)

# 模拟数据生成
def generate_test_data(filename, rows=1000000):
import random
categories = ['电子产品', '服装', '食品', '家居', '图书']

with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['category', 'amount', 'date'])

for _ in range(rows):
category = random.choice(categories)
amount = random.uniform(10, 1000)
date = f'2024-{random.randint(1,12):02d}-{random.randint(1,28):02d}'
writer.writerow([category, f'{amount:.2f}', date])

# 执行
# generate_test_data('sales.csv')
# result = process_large_csv('sales.csv')
# print(result)

🎯 本讲总结

通过本讲,我们掌握了:

知识点核心要点
列表推导式更快更简洁,适合简单转换;比普通循环快 1.5-2 倍
生成器表达式惰性求值,节省内存;只能遍历一次
元组拆包优雅的数据提取,支持 * 捕获剩余元素
命名元组给元组字段命名,提高可读性
字典推导式创建和转换字典的简洁方式
字典合并Python 3.9+ 用 | 运算符,旧版本用 ** 解包
切片强大的数据提取和修改方式,支持 step 和负索引

记住这句话

选择正确的数据容器和操作方式,能让你的代码更快、更省内存、更易读。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》

学习路线: 零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》


🎓 加入《流畅的 Python》直播共读营

学到这里,如果你想系统吃透这本书——欢迎加入我的直播共读课。

  • 每周直播精讲,逐章拆解核心知识点
  • 专属学习群,随时答疑交流
  • 试运营特惠:499 元299 元

👉 【立即报名《流畅的 Python》共读课】https://mp.weixin.qq.com/s/ivHJwn1nNx5ug4TFrapvGg


🔗 课程导航

上一讲:Python 数据模型 | 下一讲:集合与映射


💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!

fluent-python.png