Python中的生成器与yield
对于python中的yield有些疑惑,然后在StackOverflow上看到了一篇回答,所以搬运过来了,英文好的直接看原文吧。
可迭代对象
当你创建一个列表的时候,你可以一个接一个地读取其中的项。一个接一个地读项就叫做迭代:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
就是一个可迭代对象。你使用列表推导式时,就创建了一个列表,也就是一个可迭代对象:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
任何可以用“for...in...
”操作的事物都是可迭代对象;列表,字符串,文件……
这些可迭代对象很方便,因为你可以随意地访问它们,但是你把所有的值都保存在内存中。如果你有大量的数据的话,这就不是你想要的了。
生成器
生成器是迭代器,但是你只可以对它们进行一次迭代。这是因为你没有在内存中存储所有数值,它们动态地生成值:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
跟上面的例子基本一样,除了用()
代替了[]
。但是,你不能第二次执行for i in mygenerator
,因为生成器只能使用一次;它计算出0,然后忘掉0并计算1,最后是计算4,一个接一个地进行。
Yield
Yield
是一个像return
一样的关键字,只不过函数返回的是一个生成器:
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # 创建一个迭代器
>>> print(mygenerator) # mygenerator是一个对象!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
>>> for i in mygenerator:
print(i)
>>>
这个例子里的生成器没什么用,但是如果你知道你的函数会返回大量的数据,而你只需要对这些数据读取一次时,那就变得很有用了。
要掌握yield
,你必须理解当你访问函数的时候,你写在函数体中的代码并没有执行。这个函数只是返回了迭代器对象,这一点很微妙。
然后,每次for
语句使用生成器的时候,你的代码就会运行。
然后是最难的部分:
for
语句第一次调用从你的函数中创建的迭代器对象时,它就会运行你函数中的代码,从开始一直到它碰见yield
,然后它就返回这个循环中的第一个值。之后,每一次的调用都会再一次运行你写在函数里的循环,然后返回下一个值,直到没有值可以被返回。
一旦函数运行但是没有再碰到yield
,生成器就被认为是空的。这可能是因为循环已经结束了,或者因为不再满足if/else
条件了。
控制生成器耗尽
>>> class Bank(): # let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
这个在很多方面都有用,比如控制对某个资源的访问。
Itertools,好帮手
itertools模块包含操作可迭代对象的特殊函数。想要复制一个生成器?连接两个生成器?把嵌套列表中的数据整理到一个列表中?不创建另一个列表就直接Map/Zip
?
那就import itertools
。
例子?那我们看看4马比赛中所有可能的到达顺序(全排列):
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
理解迭代的内部机制
迭代是一个暗含可迭代对象(实现了__iter__()
方法)和迭代器(实现了__next__()
方法)的过程。可迭代对象是任何你可以从中得到迭代器的对象。迭代器是可以让你对可迭代对象进行迭代的对象。
更多信息请参考how does the for loop work。