引言:惰性计算的威力
在编程中,我们经常需要处理大量数据,但并非所有数据都需要同时加载到内存中。Python的迭代器和生成器提供了一种优雅的解决方案,允许我们按需处理数据,从而节省内存并提高程序效率。
迭代器和生成器是Python中强大而灵活的特性,它们实现了**惰性计算(Lazy Evaluation)**的概念,即在需要时才生成或计算值,而不是提前准备好所有数据。这种特性在处理大数据集、流式数据或无限序列时特别有用。
在本节中,我们将深入探讨迭代器和生成器的工作原理、使用方法以及在实际项目中的应用。
第一部分:迭代器基础
1.1 什么是迭代器?
迭代器是一个可以记住遍历位置的对象。它实现了迭代器协议,即包含__iter__()和__next__()方法。迭代器从集合的第一个元素开始访问,直到所有元素被访问完,且只能前进不能后退。
1.2 迭代器协议
任何实现了__iter__()和__next__()方法的对象都是迭代器:
__iter__():返回迭代器对象本身__next__():返回下一个元素,如果没有元素则抛出StopIteration异常
# 自定义迭代器示例
class CountDown:
"""倒计时迭代器"""
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
else:
self.current -= 1
return self.current + 1
# 使用自定义迭代器
countdown = CountDown(5)
for number in countdown:
print(number) # 输出: 5, 4, 3, 2, 1
1.3 内置迭代器
Python中的许多内置类型都是可迭代的,它们提供了内置的迭代器:
# 列表迭代器
numbers = [1, 2, 3, 4, 5]
iterator = iter(numbers) # 获取迭代器
print(next(iterator)) # 输出: 1
print(next(iterator)) # 输出: 2
print(next(iterator)) # 输出: 3
# 字符串迭代器
text = "Hello"
char_iterator = iter(text)
for char in char_iterator:
print(char) # 输出: H, e, l, l, o
# 字典迭代器(迭代键)
person = {"name": "Alice", "age": 30, "city": "New York"}
key_iterator = iter(person)
print(next(key_iterator)) # 输出: name
1.4 迭代器 vs 可迭代对象
需要注意的是,迭代器和可迭代对象是不同的概念:
- 可迭代对象:实现了
__iter__()方法,可以返回一个迭代器的对象 - 迭代器:实现了
__iter__()和__next__()方法的对象
# 列表是可迭代对象,但不是迭代器
numbers = [1, 2, 3]
print(hasattr(numbers, '__iter__')) # 输出: True
print(hasattr(numbers, '__next__')) # 输出: False
# iter()函数返回列表的迭代器
numbers_iterator = iter(numbers)
print(hasattr(numbers_iterator, '__iter__')) # 输出: True
print(hasattr(numbers_iterator, '__next__')) # 输出: True
第二部分:生成器基础
2.1 什么是生成器?
生成器是一种特殊的迭代器,它使用yield语句而不是return语句来返回值。生成器函数在每次调用next()时执行,直到遇到yield语句,然后暂停执行并返回 yield 的值,下次调用时从暂停的位置继续执行。
2.2 生成器函数
使用yield关键字定义的函数称为生成器函数:
def countdown(n):
"""生成器函数:倒计时"""
while n > 0:
yield n
n -= 1
# 使用生成器
gen = countdown(5)
print(next(gen)) # 输出: 5
print(next(gen)) # 输出: 4
print(next(gen)) # 输出: 3
# 使用for循环遍历生成器
for number in countdown(3):
print(number) # 输出: 3, 2, 1
2.3 生成器表达式
除了生成器函数,Python还提供了生成器表达式,语法类似于列表推导式,但使用圆括号:
# 列表推导式(立即计算)
squares_list = [x**2 for x in range(5)] # 输出: [0, 1, 4, 9, 16]
# 生成器表达式(惰性计算)
squares_gen = (x**2 for x in range(5)) # 生成器对象
print(next(squares_gen)) # 输出: 0
print(next(squares_gen)) # 输出: 1
print(next(squares_gen)) # 输出: 4
# 可以转换为列表
print(list(squares_gen)) # 输出: [9, 16] (注意:已经消耗了前三个值)
2.4 生成器的状态
生成器会记住它的状态,包括局部变量和执行位置:
def fibonacci():
"""生成斐波那契数列的生成器"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 创建生成器
fib = fibonacci()
# 获取前10个斐波那契数
for i in range(10):
print(next(fib)) # 输出: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
# 生成器保持状态,继续获取下一个
print(next(fib)) # 输出: 55
第三部分:迭代器与生成器的比较
3.1 相同点
- 都支持迭代协议
- 都可以使用
for循环遍历 - 都支持
next()函数 - 都实现惰性计算
3.2 不同点
| 特性 | 迭代器 | 生成器 |
|---|---|---|
| 实现方式 | 需要实现__iter__和__next__方法 | 使用yield关键字或生成器表达式 |
| 代码复杂度 | 相对复杂,需要定义类 | 相对简单,只需定义函数或表达式 |
| 状态管理 | 需要手动管理状态 | 自动管理状态 |
| 内存使用 | 较低 | 极低 |
| 适用场景 | 需要复杂逻辑的迭代 | 简单的数据生成或转换 |
3.3 性能比较
生成器通常比迭代器更高效,因为它们不需要创建完整的类实例:
import time
import sys
# 迭代器实现
class SquareIterator:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.n:
raise StopIteration
result = self.current ** 2
self.current += 1
return result
# 生成器实现
def square_generator(n):
current = 0
while current < n:
yield current ** 2
current += 1
# 测试性能
n = 1000000
# 迭代器性能
start_time = time.time()
iterator = SquareIterator(n)
list(iterator)
iterator_time = time.time() - start_time
# 生成器性能
start_time = time.time()
generator = square_generator(n)
list(generator)
generator_time = time.time() - start_time
print(f"迭代器时间: {iterator_time:.4f}秒")
print(f"生成器时间: {generator_time:.4f}秒")
# 测试内存使用
iterator = SquareIterator(n)
generator = square_generator(n)
print(f"迭代器大小: {sys.getsizeof(iterator)}字节")
print(f"生成器大小: {sys.getsizeof(generator)}字节")
第四部分:高级生成器特性
4.1 生成器.send()方法
生成器支持.send()方法,可以向生成器发送值:
def accumulator():
"""累加器生成器"""
total = 0
while True:
value = yield total
if value is None:
break
total += value
# 使用生成器
acc = accumulator()
next(acc) # 启动生成器
print(acc.send(10)) # 输出: 10
print(acc.send(20)) # 输出: 30
print(acc.send(5)) # 输出: 35
acc.close() # 关闭生成器
4.2 生成器.throw()方法
可以使用.throw()方法向生成器抛出异常:
def number_generator():
"""数字生成器"""
try:
n = 0
while True:
yield n
n += 1
except ValueError:
yield "ValueError occurred"
except GeneratorExit:
print("Generator closed")
gen = number_generator()
print(next(gen)) # 输出: 0
print(next(gen)) # 输出: 1
# 向生成器抛出异常
result = gen.throw(ValueError)
print(result) # 输出: ValueError occurred
gen.close() # 输出: Generator closed
4.3 生成器.yield from语法
Python 3.3引入了yield from语法,用于简化生成器的嵌套:
# 没有yield from的嵌套生成器
def nested_generator():
for i in range(3):
yield i
for j in range(3, 6):
yield j
# 使用yield from简化
def simplified_generator():
yield from range(3)
yield from range(3, 6)
print(list(nested_generator())) # 输出: [0, 1, 2, 3, 4, 5]
print(list(simplified_generator())) # 输出: [0, 1, 2, 3, 4, 5]
# yield from还可以用于委托给子生成器
def sub_generator():
yield from (x * 2 for x in range(3))
print(list(sub_generator())) # 输出: [0, 2, 4]
4.4 协程与生成器
生成器可以用作简单的协程,实现协作式多任务:
def task(name, n):
"""简单的任务协程"""
for i in range(n):
print(f"{name} is working on task {i}")
yield # 让出控制权
# 创建多个任务
tasks = [
task("Worker A", 3),
task("Worker B", 2),
task("Worker C", 4)
]
# 简单的协作式调度器
while tasks:
for task in tasks[:]:
try:
next(task)
except StopIteration:
tasks.remove(task)
第五部分:实战应用案例
5.1 处理大型文件
生成器非常适合处理大型文件,因为它们可以逐行读取而不需要将整个文件加载到内存中:
def read_large_file(file_path):
"""逐行读取大型文件的生成器"""
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
yield line.strip()
def filter_lines(file_path, keyword):
"""过滤包含关键字的行"""
for line in read_large_file(file_path):
if keyword in line:
yield line
def process_file(file_path, process_func):
"""处理文件内容的生成器"""
for line in read_large_file(file_path):
yield process_func(line)
# 使用示例
# 假设有一个大型日志文件
log_file = "server.log"
# 查找错误日志
error_logs = filter_lines(log_file, "ERROR")
for error in error_logs:
print(error)
# 处理日志行
def parse_log_line(line):
"""解析日志行"""
parts = line.split()
return {
"timestamp": parts[0] + " " + parts[1],
"level": parts[2],
"message": " ".join(parts[3:])
}
parsed_logs = process_file(log_file, parse_log_line)
for log_entry in parsed_logs:
print(log_entry)
5.2 数据管道
生成器可以连接起来形成数据管道,每个生成器处理数据的一个阶段:
def read_data(source):
"""读取数据"""
for item in source:
yield item
def filter_data(data, condition):
"""过滤数据"""
for item in data:
if condition(item):
yield item
def transform_data(data, transform_func):
"""转换数据"""
for item in data:
yield transform_func(item)
def write_data(data, output_func):
"""输出数据"""
for item in data:
output_func(item)
yield item
# 创建数据管道
def data_pipeline(source, condition, transform_func, output_func):
"""完整的数据管道"""
data = read_data(source)
data = filter_data(data, condition)
data = transform_data(data, transform_func)
data = write_data(data, output_func)
return data
# 使用数据管道
numbers = range(100)
# 定义管道组件
def is_even(n):
return n % 2 == 0
def square(n):
return n ** 2
def print_item(item):
print(f"处理结果: {item}")
# 执行管道
pipeline = data_pipeline(numbers, is_even, square, print_item)
list(pipeline) # 消耗生成器,触发处理流程
5.3 无限序列
生成器非常适合表示无限序列:
def fibonacci():
"""无限斐波那契数列生成器"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
def primes():
"""无限质数序列生成器"""
n = 2
while True:
if all(n % i != 0 for i in range(2, int(n**0.5) + 1)):
yield n
n += 1
def random_numbers(seed=1):
"""伪随机数生成器"""
current = seed
while True:
current = (current * 1103515245 + 12345) & 0x7fffffff
yield current
# 使用无限序列
fib = fibonacci()
print("前10个斐波那契数:", [next(fib) for _ in range(10)])
prime_gen = primes()
print("前10个质数:", [next(prime_gen) for _ in range(10)])
random_gen = random_numbers()
print("10个随机数:", [next(random_gen) for _ in range(10)])
5.4 状态机
生成器可以用来实现状态机,因为它们可以保持状态并在不同状态间转换:
def traffic_light():
"""交通灯状态机"""
states = ["RED", "GREEN", "YELLOW"]
index = 0
while True:
current_state = states[index]
# 根据当前状态决定等待时间和下一个状态
if current_state == "RED":
yield ("RED", 30)
index = 1
elif current_state == "GREEN":
yield ("GREEN", 45)
index = 2
elif current_state == "YELLOW":
yield ("YELLOW", 5)
index = 0
# 使用交通灯状态机
light = traffic_light()
for _ in range(6): # 模拟两个完整周期
state, duration = next(light)
print(f"交通灯: {state}, 持续时间: {duration}秒")
5.5 分页处理
生成器非常适合处理分页数据:
class PaginatedAPI:
"""分页API客户端"""
def __init__(self, base_url):
self.base_url = base_url
def fetch_page(self, page):
"""获取单页数据(模拟)"""
# 实际应用中这里会发送HTTP请求
print(f"正在获取第{page}页数据")
# 模拟响应
if page < 5:
return {
"data": [f"项目{(page-1)*10 + i}" for i in range(1, 11)],
"has_more": True
}
else:
return {
"data": [f"项目{(page-1)*10 + i}" for i in range(1, 6)],
"has_more": False
}
def get_all_items(self):
"""获取所有项目的生成器"""
page = 1
has_more = True
while has_more:
response = self.fetch_page(page)
for item in response["data"]:
yield item
has_more = response["has_more"]
page += 1
# 使用分页API
api = PaginatedAPI("https://api.example.com/items")
for item in api.get_all_items():
print(f"处理项目: {item}")
第六部分:最佳实践与注意事项
6.1 内存管理
生成器的主要优势是内存效率,但需要注意:
# 正确使用生成器节省内存
def process_large_dataset():
"""处理大型数据集"""
for item in large_data_generator(): # 使用生成器
process_item(item) # 一次处理一个项目
# 错误用法:将生成器转换为列表,失去内存优势
def process_large_dataset_wrong():
"""错误的内存使用"""
all_data = list(large_data_generator()) # 将所有数据加载到内存
for item in all_data:
process_item(item)
6.2 生成器只能使用一次
生成器是单向的,只能遍历一次:
gen = (x for x in range(3))
print(list(gen)) # 输出: [0, 1, 2]
print(list(gen)) # 输出: [] (生成器已耗尽)
# 如果需要多次使用,可以重新创建生成器或转换为列表
gen = (x for x in range(3))
data = list(gen) # 转换为列表
print(data) # 输出: [0, 1, 2]
print(data) # 输出: [0, 1, 2] (可以多次使用)
6.3 异常处理
正确处理生成器中的异常:
def safe_generator(iterable):
"""安全的生成器,处理迭代中的异常"""
iterator = iter(iterable)
while True:
try:
item = next(iterator)
except StopIteration:
break
except Exception as e:
print(f"处理项目时发生错误: {e}")
continue
yield item
# 使用安全生成器
problematic_data = [1, 2, "not_a_number", 4, 5]
def convert_to_int(x):
"""尝试转换为整数"""
try:
return int(x)
except ValueError:
raise ValueError(f"无法转换为整数: {x}")
safe_gen = safe_generator(convert_to_int(x) for x in problematic_data)
print(list(safe_gen)) # 输出: 1, 2, 4, 5 (跳过错误项)
6.4 性能考虑
在性能敏感的场景中,考虑使用生成器表达式代替列表推导式:
# 列表推导式(立即计算所有值)
result = sum([x**2 for x in range(1000000)])
# 生成器表达式(惰性计算)
result = sum(x**2 for x in range(1000000)) # 更高效的内存使用
第七部分:常见面试题解析
7.1 Q:迭代器和生成器有什么区别?
A:主要区别在于:
- 实现方式:迭代器需要实现
__iter__()和__next__()方法;生成器使用yield关键字 - 代码复杂度:迭代器通常需要定义类;生成器只需要定义函数
- 状态管理:迭代器需要手动管理状态;生成器自动管理状态
- 内存使用:迭代器内存使用较低;生成器内存使用极低
7.2 Q:生成器有什么优点?
A:生成器的主要优点包括:
- 内存效率:按需生成值,不需要预先分配内存
- 代码简洁:使用
yield关键字简化迭代器实现 - 状态保持:自动保持执行状态
- 惰性计算:只在需要时计算值,提高性能
7.3 Q:如何在生成器中发送值?
A:可以使用生成器的.send()方法向生成器发送值:
def generator_with_send():
value = yield "Ready"
yield f"Received: {value}"
gen = generator_with_send()
print(next(gen)) # 输出: Ready
print(gen.send("Hello")) # 输出: Received: Hello
7.4 Q:什么是yield from语法?
A:yield from是Python 3.3引入的语法,用于简化生成器的嵌套:
def sub_generator():
yield from range(3)
def main_generator():
yield from sub_generator()
yield from range(3, 6)
print(list(main_generator())) # 输出: [0, 1, 2, 3, 4, 5]
7.5 Q:生成器可以用作协程吗?
A:是的,生成器可以用作简单的协程,实现协作式多任务。Python 3.5引入了原生的async/await语法后,协程的实现更加规范,但生成器仍然可以用于简单的协程场景。
总结
迭代器和生成器是Python中强大而灵活的特性,它们提供了高效的内存使用和简洁的代码实现。通过本章的学习,你应该能够:
- 理解迭代器和生成器的工作原理和区别
- 创建自定义的迭代器和生成器
- 在实际项目中选择合适的迭代方式
- 使用生成器处理大型数据集和流式数据
- 应用高级生成器特性如
.send()和yield from
迭代器和生成器是函数式编程和惰性计算的重要实现,它们可以帮助你编写更加高效和优雅的Python代码。
思考题:
- 如何实现一个生成器,能够同时迭代多个序列?
- 在使用生成器处理文件时,如何确保文件资源被正确释放?
- 如何创建一个生成器,能够根据条件动态选择不同的数据源?
- 在什么情况下,使用列表比使用生成器更合适?
- 如何测试生成器的正确性和性能?
1451

被折叠的 条评论
为什么被折叠?



