无论你在生成器中调用生成器,你都需要一个“泵”来重新yield的值:for v in inner_generator: yield v。正如PEP所指出的,大多数人忽视了这其中微妙的复杂性。像throw()这样的非本地流控制就是PEP中给出的一个例子。新语法yield from inner_generator用于以前编写显式的for循环的任何地方。不过,它不仅仅是语法糖:它处理所有被for循环忽略的极端情况。“含糖”鼓励人们使用它,从而养成正确的行为。
# chain from itertools:
def chain(*iters):
for it in iters:
for item in it:
yield item
# with the new keyword
def chain(*iters):
for it in iters:
yield from it
让我们先解决一件事。yield from g与for v in g: yield v、难道这还不够公正吗等价于yield from的解释。因为,让我们面对现实吧,如果yield from所做的只是扩展for循环,那么它就不能保证将yield from添加到语言中,并排除在Python 2.x中实现一大堆新特性。
def reader():
"""A generator that fakes a read from a file, socket, etc."""
for i in range(4):
yield '<< %s' % i
def reader_wrapper(g):
# Manually iterate over data produced by reader
for v in g:
yield v
wrap = reader_wrapper(reader())
for i in wrap:
print(i)
# Result
<< 0
<< 1
<< 2
<< 3
def writer_wrapper(coro):
# TBD
pass
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in range(4):
wrap.send(i)
# Expected result
>> 0
>> 1
>> 2
>> 3
包装器需要接受发送给它的数据(显然),并且当for循环耗尽时也应该处理StopIteration。显然,只做for x in coro: yield x是不行的。这里有一个有效的版本。
def writer_wrapper(coro):
coro.send(None) # prime the coro
while True:
try:
x = (yield) # Capture the value that's sent
coro.send(x) # and pass it to the writer
except StopIteration:
pass
class SpamException(Exception):
pass
def writer():
while True:
try:
w = (yield)
except SpamException:
print('***')
else:
print('>> ', w)
如果我们不改变writer_wrapper会怎样?这有用吗?让我们尝试
# writer_wrapper same as above
w = writer()
wrap = writer_wrapper(w)
wrap.send(None) # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
if i == 'spam':
wrap.throw(SpamException)
else:
wrap.send(i)
# Expected Result
>> 0
>> 1
>> 2
***
>> 4
# Actual Result
>> 0
>> 1
>> 2
Traceback (most recent call last):
... redacted ...
File ... in writer_wrapper
x = (yield)
__main__.SpamException
def writer_wrapper(coro):
"""Works. Manually catches exceptions and throws them"""
coro.send(None) # prime the coro
while True:
try:
try:
x = (yield)
except Exception as e: # This catches the SpamException
coro.throw(e)
else:
coro.send(x)
except StopIteration:
pass
def flatten(sequence):
"""flatten a multi level list or something
>>> list(flatten([1, [2], 3]))
[1, 2, 3]
>>> list(flatten([1, [2], [3, [4]]]))
[1, 2, 3, 4]
"""
for element in sequence:
if hasattr(element, '__iter__'):
yield from flatten(element)
else:
yield element
print(list(flatten([1, [2], [3, [4]]])))
def iter_fun(sum, deepness, myString, Total):
if deepness == 0:
if sum == Total:
yield myString
else:
for i in range(min(10, Total - sum + 1)):
yield from iter_fun(sum + i,deepness - 1,myString + str(i),Total)
def fixed_sum_digits(digits, Tot):
return iter_fun(0,digits,"",Tot)
def gen(sequence):
for i in sequence:
yield i
def merge_batch(sub_seq):
yield {"data": sub_seq}
def modified_gen(g, batch_size):
stream = []
for i in g:
stream.append(i)
stream_len = len(stream)
if stream_len == batch_size:
yield from merge_batch(stream)
print("batch ends")
stream = []
stream_len = 0
运行这个程序会得到:
In [17]: g = gen([1,2,3,4,5,6,7,8,9,10])
In [18]: mg = modified_gen(g, 2)
In [19]: next(mg)
Out[19]: {'data': [1, 2]}
In [20]: next(mg)
batch ends
Out[20]: {'data': [3, 4]}
In [21]: next(mg)
batch ends
Out[21]: {'data': [5, 6]}
In [22]: next(mg)
batch ends
Out[22]: {'data': [7, 8]}
In [23]: next(mg)
batch ends
Out[23]: {'data': [9, 10]}
In [24]: next(mg)
batch ends
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Input In [24], in <cell line: 1>()
----> 1 next(mg)
StopIteration: