结构化编程与Python生成器?
更新:我一直想要的其实是greenlets。
注意:这个问题在大家回答的过程中有些变化,让我不得不“提高要求”,因为我最开始的简单例子太简单了;为了不在这里继续变化,我会在我脑子里理清楚后再重新提问,按照Alex的建议。
Python的生成器真是太美妙了,但我该如何轻松地把它拆分成模块(结构化编程)呢?我实际上想要的是PEP 380,或者至少是语法负担相当的东西,但在现有的Python中(比如2.6)。
举个(确实有点傻的)例子,看看下面的代码:
def sillyGenerator():
for i in xrange(10):
yield i*i
for i in xrange(12):
yield i*i
for i in xrange(8):
yield i*i
作为一个坚定的DRY(不要重复自己)信徒,我发现了这里的重复模式,于是把它提取成一个方法:
def quadraticRange(n):
for i in xrange(n)
yield i*i
def sillyGenerator():
quadraticRange(10)
quadraticRange(12)
quadraticRange(8)
...但这当然不管用。父级必须在一个循环中调用这个新函数,得到结果:
def sillyGenerator():
for i in quadraticRange(10):
yield i
for i in quadraticRange(12):
yield i
for i in quadraticRange(8):
yield i
...这比之前还要长呢!
如果我想把生成器的一部分放到一个函数里,我总是需要这个相当冗长的两行代码来调用它。如果我还想支持send(),那就更麻烦了:
def sillyGeneratorRevisited():
g = subgenerator()
v = None
try:
while True:
v = yield g.send(v)
catch StopIteration:
pass
if v < 4:
# ...
else:
# ...
而且这还没考虑到传递异常。每次都要写一样的模板代码!但又不能应用DRY,把这些相同的代码提取成一个函数,因为...你还得用模板代码来调用它!我想要的就是这样的:
def sillyGenerator():
yield from quadraticRange(10)
yield from quadraticRange(12)
yield from quadraticRange(8)
def sillyGeneratorRevisited():
v = yield from subgenerator()
if v < 4:
# ...
else:
# ...
有没有人有解决这个问题的办法?我有一个初步的尝试,但我想知道别人有什么想法。最终,任何解决方案都必须处理一些例子,其中主生成器根据发送到生成器的数据结果执行复杂的逻辑,并可能会对子生成器进行大量调用:我的使用场景是生成器用于实现长时间运行的复杂状态机。
8 个回答
不太实际(可惜)答案:
from __future__ import PEP0380
def sillyGenerator():
yield from quadraticRange(10)
yield from quadraticRange(12)
yield from quadraticRange(8)
可能有用的参考资料:用于委托给子生成器的语法
可惜的是,这让事情变得不太实际:Python语言暂停
更新 2011年2月:
暂停已经解除,PEP 380 已经列入 Python 3.3 的待办事项列表。希望这个答案很快就能变得实际可用。
你想把几个迭代器连在一起:
from itertools import chain
def sillyGenerator(a,b,c):
return chain(quadraticRange(a),quadraticRange(b),quadraticRange(c))
不过,我想让我的可重用性标准更严格一点:如果我需要在重复生成的周围加一个控制结构呢?
itertools
通常在这方面也能提供帮助——你需要提供具体的例子,说明你认为它不适用的地方。
比如,我可能想要用不同的参数永远调用一个子生成器。
itertools.chain.from_iterable
。
或者我的子生成器可能非常耗费资源,我只想在需要的时候才启动它们。
无论是 chain
还是 chain_from_iterable
都可以做到这一点——在需要第一个项目之前,子迭代器不会被“启动”。
或者(这真的是一个需求)我可能想根据我的控制器通过
send()
传递给我的内容来改变我接下来要做的事情。
一个具体的例子会非常受欢迎。总之,最糟糕的情况是,你会写 for x in blargh: yield x
,而暂停的 Pep3080 让你可以写 yield from blargh
——多了大约 4 个字符(这不算什么悲剧;-)。
如果某些复杂的协程版本的 itertools
功能(因为 itertools
主要支持迭代器——目前还没有等效的 coroutools
模块)变得必要,因为某种协程组合模式在你的代码中经常重复,那么自己编写它也不难。
例如,假设我们经常需要做这样的事情:首先产出一个特定的值;然后,如果我们收到 'foo',就从 fooiter
中产出下一个项目,如果是 'bla',就从 blaiter
中产出,如果是 'zop',就从 zopiter
中产出,其他情况则从 defiter
中产出。只要我们发现这种组合模式出现第二次,就可以编写:
def corou_chaiters(initsend, defiter, val2itermap):
currentiter = iter([initsend])
while True:
val = yield next(currentiter)
currentiter = val2itermap(val, defiter)
并在需要的时候调用这个简单的组合函数。如果我们需要组合其他协程,而不是一般的迭代器,我们会有一个稍微不同的组合器,使用 send
方法而不是内置的 next
函数;等等。
如果你能提供一个不容易用这些技术解决的例子,我建议你在一个单独的问题中提出(特别针对类似协程的生成器),因为关于这个主题已经有很多材料,而这些材料与你其他更复杂/更精细的例子关系不大。