迭代器和生成器
1. 迭代器
1.1 迭代器规则
-
迭代就是重复做一些事多次
-
对象只要实现__iter__方法就可以对对象进行迭代,__iter__方法返回一个迭代器(iterator)
-
迭代器就是实现了next方法,在调用next时返回下一个值,如果next调用了,但是迭代器没有值可以返回,引发一个StopIteration异常
在python 3中,迭代器应该实现__next__方法,不是next,新增内建函数next访问此方法,next(it)等同与it.next()
-
正式说法是一个实现了__iter__方法的的对象是可迭代的,一个实现了next方法的对象则是迭代器
-
使用list可以显示把迭代器转换为列表
2. 生成器
生成器是python新引入的概念,也叫简单生成器,可以理解为迭代器的简单版本,迭代器工作在类级别,生成器工作在函数级别。
生成器是用普通函数语法定义的迭代器
2.1 创建生成器
- 任何包含yield语句的函数称为生成器,定义生成器与普通函数基本相同,只需要吧return替换为yield语句
- 生成器的行为和普通函数有很多区别,普通函数return后函数空间释放了,生成器yield产生一个新的值后函数会被冻结,停止yield语句,下次重新被唤醒后从上次yield的地方继续执行。
>>> def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
>>> nested = [['1', '2'], ['3', '4'], ['5', '6']]
>>> for num in flatten(nested):
print num,
1 2 3 4 5 6
2.2 生成器推导式
- 生成器推导式和列表推导式工作方式类似,只不过放回的不是列表而是生成器(并且不会立即进行循环),所返回的生成器允许一步一步的进行计算
- 生成器推导式和列表推导式不同的是使用圆括号
- 如果希望将可迭代对象”打包“(比如生成大量的值),那么最好不要用列表推导式,因为列表推导式会立即实例化一个列表,从而丧失迭代的优势
>>> g = ((i + 2) ** 2 for i in range(2, 27))
>>> g
<generator object <genexpr> at 0x03208C60>
>>> g.next()
16
>>> hasattr(g, '__iter__')
True
# 更妙的地方在于生成器推导式可以在当前的圆括号中直接使用,如在函数调用中可以不用增加另外一对圆括号
>>> sum(i ** 2 for i in range(10))
285
2.3 递归生成器
- 如上面的展开嵌套列表的方式只能展开2层嵌套,如果需要处理更多情况,则需要更灵活的方案,使用递归
>>> def flatten(nested):
try:
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
>>> list(flatten([[], [1, 2], [[3], [4]], 1]))
[1, 2, 3, 4, 1]
# 这么做有一问题,当nested是一个类似字符串的对象,那么它就不会引发TypeError,会出现无穷递归
# 解决这个问题需要在开始处增加一个检查语句,试着将传入对象和一个字符串进行拼接,看会不会有TypeError,这是检查一个对象是不是类似于字符串的最简单,最快速的方法
>>> def flatten(nested):
try:
try: nested + ''
except TypeError: pass
else: raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
>>> list(flatten([[], [1, 2], [[3], [4]], 1, '123']))
[1, 2, 3, 4, 1, '123']
>>> list(flatten([[], [1, 2], [[3], [4]], 1, '123', {'abc': 'val1'}]))
[1, 2, 3, 4, 1, '123', 'abc']
2.4 通用生成器
- 生成器是一个包含yield关键字的函数,当它被调用是不会立即执行,而是返回一个迭代器,每请求一个值,就会执行生成器中的代码,直到遇到一个yield或return语句,yield语句意味着生产一个值,return语句意味着生成器要停止执行
- 生成器由生成器的函数和生成器的迭代器两部分组成
- 生成器的函数,用def语句定义的,包含yield的部分
- 生成器的迭代器,调用生成器函数的放回值
2.5 生成器的方法
-
send
- 外部作用域访问生成器的send方法和访问next方法一样,只不过send可以使用一个参数
- 在内部则挂起生成器,yield作为表达式使用而不是语句使用,就是生成器重新运行时yield返回send的值,如果next被调用,则返回是None
使用send方法只有生成器挂起后才有意义(也就是在yield函数第一次执行以后),如果在此之前需要给生成器更多信息,那么使用生成器函数参数
-
next,生成器产生一个新值
-
throw,生成器内引发一个异常,
-
close,停止生成器,close方法也是建立在异常的基础上,它在yield运行处引发一个GengeratorExit异常
>>> def repeater(value):
while True:
new = (yield value)
print 'new is', new
>>> r = repeater(3)
>>> r.send('Hello')
Traceback (most recent call last):
File "<pyshell#221>", line 1, in <module>
r.send('Hello')
TypeError: can't send non-None value to a just-started generator
>>> r.next()
3
>>> r.send('Hello')
new is Hello
3
>>> r.send('43')
new is 43
3
>>> r.send('44')
new is 44
3
>>> r.next()
new is None
3
3. 八皇后问题
-
八皇后问题是经典的回溯算法,使用生成器可以很好的解决
#!/bin/sh # --*-- coding: utf-8 --*-- # 1. 确定如何存储解决位置信息,根据问题分析,最终解决方案中每一行只能有一个皇后,只需存储没一行的皇后所在的列位置即可,使用元组state,其中state[i]表示第i行的皇后列的位置,起始位置为0 # 2. 冲突判断,新加入一个皇后,判断是否会出现冲突 # 在下一行的某一列中放置一个皇后,判断是否和已有的产生冲突 def confict(state, nextX): """ 在下一行的nextY位置放置一个皇后,检测会产生冲突,遍历state中的每个皇后,判断是在同一列,或者行距离和列的距离相等 """ nextY = len(state) for y in range(nextY): if abs(state[y] - nextX) in (0, nextY - y): return True return False # 3. 扩展为任意皇后问题 def queen(num=8, state=()): for pos in range(num): if not confict(state, pos): if len(state) == num - 1: yield state + (pos,) else: for res in queen(num, state + (pos,)): yield (pos,) + res # 4. 非递归方式 def queen2(num = 4): states = [()] lst = range(num) while len(states[0]) < num: state = states.pop(0) for pos in lst: if not confict(state, pos): states.append(state + (pos,)) return states