翻译自: https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do
基本理解
为了理解yield管自己的作用,你需要先理解什么是生成器(generators).理解生成器之前,需要先理解可迭代对象(iterables)
可迭代对象(iterables)
当你创建一个list对象时,你可以逐个成员去遍历,这种逐个成员遍历读取的操作叫做迭代(iteration)
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
...
1
2
3
这里mylist就是一个可迭代对象。如果你使用列表解析(list comprehension,或翻译成列表推导),你创建一个列表的同时,也创建了一个可迭代对象
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
...
0
1
4
每一种你可以使用for…in…语法操作的对象都是可迭代对象,比如lists、strings、files…
这些可迭代对象使用起来非常方便,你可以在需要的时候任意读取使用,因为所有的数据都存在内存(memory)里。然而你并不总是希望这些数据都存在内存里,尤其是当可迭代对象的数据量非常大的时候
生成器(generators)
生成器是迭代器(iterators),一种只能迭代一次的可迭代对象。生成器不会把所有的数据都存在内存里,而是在执行时,动态生成数据(generate values on the fly)
>>> mygenerator = ( x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
...
0
1
4
这段代码和上一段相比,除了"[]“换成了”()",其他逻辑完全一样,但是这里的mygenerator已经不再是一个列表,而是一个生成器了。生成器只能被遍历一次,所以这里不能第二次执行for i in mygenerator
。在第一个for…in…循环中,计算00,打印0,丢弃0 --> 计算11,打印1,丢弃1 --> 计算2*2,打印4,丢弃4
yield
yield是一个跟return用法类似的关键字,区别在于前者返回的是一个生成器
def createGenerator():
mylist = range(3)
for i in mylist:
yield i*i
mygenerator = createGenerator()
print(mygenerator)
for i in mygenerator:
print(i)
执行结果
<generator object createGenerator at 0x03662B30>
0
1
4
这个是个没啥用的例子,但是当你的函数会返回一个巨大的数据集,而且这个数据集你只会使用一次时,这种方式用起来就很方便了
为了理解yield,你必须理解当你调用你的函数时,你在这个函数里面写的代码并不会被执行,这个函数只会返回一个生成器对象。 这个听起来有点儿迷惑(a bit tricky)
然后,当遇到for代码处理生成器时,代码才会从中断的地方继续执行
现在,最难的部分:
第一次用for语句访问你的函数返回的生成器时,代码会从函数的开头一直执行到第一个yield处,返回第一次yield的值,后面的其他继续访问这个生成器的调用,将会从这个yield之后继续执行到下一次遇到yield,返回yield的值,依此执行,直到没有返回值
一旦函数执行完(逻辑上:因为循环结束或者不满足if/else条件导致的不会再执行yield操作),执行器中不会再有值
进阶操作
控制一个生成器的消耗(controlling a generator exhaustion)
>>> 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 # The 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
...
说明:python3里面,用next(corner_street_atm)或者corner_street_atm.next()代替corner_street_atm.next(),其他生成器亦然
这种操作针对各种与控制资源读取类似的场景非常有用
itertools, your best friend
itertools库包含管理迭代对象的各种特殊函数。可以实现诸如复制生成器、连接两个生成器、对交织在一个list里面的数据进行分组、在不创建新的list的情况下实现map/zip等操作。使用时直接import itertools
即可
示例:一个四匹马的赛马所有可能的到达顺序
>>> 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)]
理解迭代的内部机制
迭代(iteration)是一中对可迭代对象(实现了__iter__()这个魔术方法)和迭代器(实现了__next__()这个魔术方法)的操作。可迭代对象(iterable)是包含迭代器的一种python对象。迭代器(iterator)是一个让你能够遍历一个可迭代对象的python对象