第 8 章 Python可迭代对象
迭代器(iterator
)有时又称游标(cursor)是程式设计的软件设计模式,可在容器物件(container,例如链表或阵列)上遍访的界面,设计人员无需关心容器物件的内存分配的实现细节。
8.1 可迭代对象
Python中经常使用for
来对某个对象进行遍历,此时被遍历的这个对象就是可迭代对象,像常见的序列(字符串、列表、元组)、字典都是。如果给一个准确的定义的话,就是只要它定义了可以返回一个迭代器的__iter__
方法,或者定义了可以支持下标索引的__getitem__
方法,那么它就是一个可迭代对象。
8.2 迭代器
迭代器是通过next()
来实现的,每调用一次他就会返回下一个元素,当没有下一个元素的时候返回一个StopIteration
异常,所以实际上定义了这个方法的都算是迭代器。
8.3 生成器
生成器是构造迭代器的最简单有力的工具,与普通函数不同的只有在返回一个值的时候使用yield
来替代return
,然后yield
会自动构建好next()
和iter()
。
8.4 三者之间关系
- 可迭代对象包含迭代器。
- 如果一个对象拥有
__iter__
方法,其是可迭代对象;如果一个对象拥有next
方法,其是迭代器。 - 定义可迭代对象,必须实现
__iter__
方法;定义迭代器,必须实现__iter__
和next
方法。
8.5 迭代器长度的计算
迭代器(包括生成器)是不能直接使用len()
方法计算长度的,例如:
>>>l = (i for i in xrange(100) if i&1)
>>>len(l)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'generator' has no len()
计算迭代器的长度,我们可以先将其转化为列表再计算,但如果迭代器规模较大,这将消耗大量内存,并不是很好的解决方案:
我们可以使用更为简洁的方式,即通过循环求和的方式得到迭代器的长度:
在此基础上,我们可以定义一个函数,专门用来求迭代器(生成器)的长度:
8.6 yield
可以将yield简单类比为return,但是它除了返回一个值,还会记住这个返回的位置,下次迭代就从这个位置后(下一行)开始。yield与retrun语句最大的差别是,return语句之后的代码是不执行的,而yield语句之后的代码依然得到执行。
def yield_test(n):
for i in range(n):
yield call(i)
print("i=",i)
print("yield函数第 %d 次迭代结束"%(i))
print('yield函数整体结束')
def call(i):
return i*2
#使用for循环获取迭代器中的值
for i in yield_test(5):
print('yield 函数的返回值是:',i)
上述代码的输出结果为:
yield 函数的返回值是: 0
i= 0
yield函数第 0 次迭代结束
yield 函数的返回值是: 2
i= 1
yield函数第 1 次迭代结束
yield 函数的返回值是: 4
i= 2
yield函数第 2 次迭代结束
yield 函数的返回值是: 6
i= 3
yield函数第 3 次迭代结束
yield 函数的返回值是: 8
i= 4
yield函数第 4 次迭代结束
yield函数整体结束
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()
(在 for
循环中会自动调用 next()
)才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next()
的值,不仅代码简洁,而且执行流程异常清晰。