Blog
Blog copied to clipboard
迭代器和生成器
迭代器和生成器
迭代器 (Iterator)
迭代器对象
:必须实现 __iter__()
, __next__()
这两个特殊方法的对象,另外,又把实现这两个特殊方法统称为 迭代器协议
.
其中,__iter__()
方法返回迭代器对象自身,__next__()
方法返回序列的下一个值,如果序列中没有更多元素了,则抛出 StopIteration
异常.
>>> class PowTow:
... def __init__(self, max=0):
... self.max = max
... self.n = 0
... def __iter__(self):
... return self
... def __next__(self):
... if self.n <= self.max:
... result = 2 ** self.n
... self.n += 1
... return result
... else:
... raise StopIteration
...
>>> p = PowTow(2)
>>> next(p)
1
>>> next(p)
2
>>> next(p)
4
>>> next(p)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 13, in __next__
StopIteration
注意: 迭代器只能迭代一次,每次调用
next()
方法就会向前一步,不能回退.
>>> t = PowTow(2)
>>> t
<__main__.PowTow object at 0x7f3022a4aa20>
>>> for i in t:
... print(i)
...
1
2
4
>>> for h in t:
... print(h)
...
>>>
for
循环,其本质就是通过不断调用 next()
函数实现的.
for x in [1, 2, 3, 4, 5]:
pass
等价于
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
回到迭代器无法多次使用的问题上来,从上面给出的 Demo 例子看,有两个解决方案:
- 在
__iter__()
方法,重置n
的值 - 将可迭代对象和迭代器分开自定义
什么是可迭代对象,看下文介绍
方案一:在 __iter__()
方法重置 n
的值
>>> class PowTow:
... def __init__(self, max=0):
... self.max = max
... def __iter__(self):
... self.n = 0
... return self
... def __next__(self):
... if self.n <= self.max:
... result = 2 ** self.n
... self.n += 1
... return result
... else:
... raise StopIteration
...
>>> next(p) # 这里,我们要先 iter(p) 调用 __iter__() 方法初始化 n 值
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __next__
AttributeError: 'PowTow' object has no attribute 'n'
>>> iter(p)
<__main__.PowTow object at 0x7f3022a4ad30>
>>> next(p)
1
>>> next(p)
2
>>> next(p)
4
>>> next(p)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 13, in __next__
StopIteration
>>> m = PowTow(2)
>>> for i in m:
... print(i)
...
1
2
4
>>> for a in m:
... print(a)
...
1
2
4
第一次调用
for
循环:for i in m
的时候,在结束循环的时候,默认里已经抛出StopIteration
异常,此时self.n
值为3
当第二次调用for
循环:for a in m
的时候,for
循环默认调用了__iter__()
方法,此时self.n
值由3
重置为0
,因此不触发抛出StopIteration
异常的条件,迭代器可再次使用.
方案二:将可迭代对象和迭代器分开自定义
>>> class PowTow:
... def __init__(self, max=0):
... self.max = max
... self.n = 0
... def __iter__(self):
... return self
... def __next__(self):
... if self.n <= self.max:
... result = 2 ** self.n
... self.n += 1
... return result
... else:
... raise StopIteration
...
>>> class Pow:
... def __init__(self, max=0):
... self.max = max
... def __iter__(self):
... return PowTow(self.max)
...
>>> p = Pow(2)
>>> for a in p:
... print(a)
...
1
2
4
>>> for b in p:
... print(b)
...
1
2
4
p
是一个可迭代对象,每次for
循环迭代p
的时候,__iter__()
方法都返回一个新的PowTow
迭代器,新的迭代器的self.n
值都是初始化值:0
,不会触发抛出StopIteration
异常的条件
这里要注意区分下:可迭代对象可以多次重复迭代使用,而迭代器,大多数情况下,只能迭代一次
可迭代对象 (Iterables)
可迭代对象
:实现了 __iter__()
方法,该方法返回一个迭代器对象,或者定义了 __getitem__()
方法,可以按 index
索引的对象,并且在没有值时抛出 IndexError
异常.
可迭代对象都可以通过 iter()
方法返回一个 迭代器
.
如,上面例子中的 Pow
就是一个可迭代对象.
生成器 (Generator)
生成器
:其实是一个特殊的 迭代器
,不过它比常规的 迭代器
更加优雅,只需要一个 yield
关键字即可.
生成器实现的斐波那契数列
>>> def fib():
... prev, curr = 0, 1
... yield prev
... yield curr
... while True:
... prev, curr = curr, prev + curr
... yield curr
...
>>> f = fib()
>>> a = [next(f) for i in range(3)]
>>> a
[0, 1, 1]
>>> b = [next(f) for i in range(8)]
>>> b
[2, 3, 5, 8, 13, 21, 34, 55]
>>> c = fib()
>>> d = [next(c) for j in range(13)]
>>> d
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
注意,生成器大多数情况下也只是迭代一次(生成器是一个特殊的迭代器)
在 Python
中主要有两类生成器:
- 生成器函数
- 生成器表达式
生成器函数
:指在函数体中定义了 yield
关键字的函数,例如上面例子中的 fib()
函数.
生成器表达式
:类似列表推导表达式,不过它们之间有一点点不同.
# 列表推导表达式
>>> a = [i for i in range(5)]
>>> a
[0, 1, 2, 3, 4]
# 生成器表达式
>>> b = (i for i in range(5))
>>> b
<generator object <genexpr> at 0x7f3022a56518>
>>> list(b)
[0, 1, 2, 3, 4]
它们之间的区别就是,列表推导表达式,使用中括号:
[ ]
,而生成器表达式,使用小括号:( )
有时,生成器表达式的小括号可以省略不写,例如:
>>> sum(i for i in range(5))
10
参考资料:
完全理解Python迭代对象、迭代器、生成器
Iterables vs. Iterators vs. Generators
对 Python 迭代的深入研究
Python Iterators