数据容器
面向对象的迭代器
我们在前文已经多次提及迭代器了,并且介绍了使用生成器创建迭代器的方法。这一节,我们深入讨论一下迭代器内部构造和工作原理,以及如何使用面向对象的编程方式实现迭代器。
迭代器协议
迭代器协议规定了两个特殊的方法:__iter__() 和 __next__(),这意味着实现了这两个方法的对象,就可以被当做迭代器。
__iter__()方法: 返回迭代器对象本身。针对迭代器运行 iter() 函数,或是在 for 循环中去迭代一个迭代器的时候,调用的就是这个方法。__next__()方法: 返回迭代器中的下一个元素。针对迭代器运行 next() 函数时,调用的就是这个方法。如果没有更多元素可用,__next__()应该抛出 StopIteration 异常。每次调用__next__()方法时,迭代器都会记住当前的状态,以便在下次调用时从上次离开的地方继续。
从协议中可以看出,迭代器是惰性求值的,它不会在一开始就计算出所有元素,而是在每次调用 next() 时才产生一个元素。这使得迭代器特别适用于处理大量或无限的数据集合。迭代器只能向前移动,不能回退到之前的元素,也不能复制迭代器的状态。
下面是一个自定义迭代器的示例,该迭代器能够返回一个递增的数列,直到达到一个上限:
class CountUpTo:
def __init__(self, max):
self.max = max
self.num = 0 # 初始化 self.num
def __iter__(self):
return self
def __next__(self):
if self.num < self.max:
result = self.num
self.num += 1
return result
else:
raise StopIteration
# 创建迭代器
counter = CountUpTo(5)
# 测试
print(next(counter)) # 输出: 0
print(next(counter)) # 输出: 1
print(next(counter)) # 输出: 2
print(next(counter)) # 输出: 3
print(next(counter)) # 输出: 4
# print(next(counter)) # 已经到头,再调用 next 会产生 StopIteration 异常
上面程序实现的 CountUpTo 类与我们前文介绍的生成器函数 count_up_to() 函数的功能是完全相同的。
通过可迭代对象实例化迭代器
程序中经常使用迭代器,很多都不是我们自己写的,而是从从可迭代对象那里获得的。比如最常见的列表,使用 iter() 函数可以从可迭代对象那里获得一个迭代器:
my_list = [1, 2, 3]
my_iter = iter(my_list)
print(next(my_iter)) # 输出 1
print(next(my_iter)) # 输出 2
在这个例子中,my_list 是一个可迭代对象,my_iter 是从 my_list 创建的迭代器。
itertools 库
Python 在 itertools 标准库模块中提供了一系列的迭代器,可以帮助我们进行复杂的迭代操作,比如组合数据、过滤数据等。使用 itertools 可以使代码更加简洁、高效、易于阅读。其实我们在前面的内容中已经介绍过好几个 itertools 库中的迭代器了,这里再集中介绍几个最常用的迭代器。
无限迭代器
- count(start=0, step=1): 从 start 开始,无限地产生等差数列。
- cycle(iterable): 无限地重复给定的序列。
- repeat(object[, times]): 重复一个对象,无限次或指定次数。
在生成素数序列时,我们曾经利用过这个 count 迭代器。
from itertools import count, cycle, repeat
# 从 10 开始的无限等差数列,步长为 2
for num in count(10, 2):
if num > 20: # 为了避免无限循环,添加一个退出条件
break
print(num)
# 无限重复列表 [1, 2, 3]
count = 0
for item in cycle([1, 2, 3]):
if count > 8: # 为了避免无限循环,添加一个退出条件
break
print(item)
count += 1
# 重复字符串 "Hello" 5 次
for item in repeat("Hello", 5):
print(item)
在使用这类可以无限产生元素的迭代器时,要小心避免造成死循环。也要避免把无限迭代器作为参数传递给函数,比如 print(*count()) 会尝试解包生成器能生成的所有的值,造成内存耗尽。
使用无限迭代器时,需要使用条件判断或限制迭代次数来避免死循环,内存耗尽等问题。
有限迭代器
有限迭代器种类繁多,我们使用示例分别介绍一下:
累积结果
accumulate(iterable[, func, *, initial=None]): 返回累积总和或其他二元函数的累积结果。
from itertools import accumulate
import operator
# 累加
data = [1, 2, 3, 4, 5]
result = list(accumulate(data))
print(result) # 输出: [1, 3, 6, 10, 15]
# 累积乘积
result = list(accumulate(data, operator.mul))
print(result) # 输出: [1, 2, 6, 24, 120]
accumulate 的功能与 reduce() 函数有些类似。最主要的区别在于 reduce() 函数只返回 最终的累积结果,而 accumulate 会把中间累积运算的每一步过程都返回回来。
连接
chain(*iterables): 连接多个迭代器为一个长序列。
from itertools import chain
# 连接多个列表
result = list(chain([1, 2, 3], ['a', 'b', 'c']))
print(result) # 输出: [1, 2, 3, 'a', 'b', 'c']
chain() 函数在效果上与 + 运算符非常类似,它们都可以将多个序列连接在一起。区别在于, chain() 函数可以接收任何迭代对象,返回的是一个迭代器;而 + 运算符要求操作的对象类型相同(例如两个列表或两个元组),其返回结果是与输入类型相同的一个新的完整序列,而不是迭代器。chain() 函数有更高的内存效率,适合处理大数据。