函数bigop(init, report)
对从data
派生的大型动态内部数据结构init
进行操作,并接受可调用的report(data)
。函数status(data)
返回data
的当前状态摘要。
功能bigop
在report
的每个主要步骤调用data
,然后调用status
。复制data
用于每个步骤(或使其持久)会很昂贵,因此report
必须在bigop
继续之前的每一步完成。
函数view(gen)
接受生成器gen
,生成状态摘要的连续值,并在生成时显示每个值的可视化。函数view
依赖于迄今为止生成的值来维护内部状态。 (在我的特定情况下,这个内部状态可以被复制,但避免它会很好。)
假设无法更改函数bigop
和view
。
问题:如何定义gen
,report
和程序main
以使bigop
在init
上运行,以及值的可视化状态报告将显示 bigop
到达每个主要步骤?
困难在于report
和gen
在其他函数内部被调用,因此通常的Python协程模式不适用。 (在我的特定情况下,bigop
实际上是一个生成器。)
A previous question使用线程来回答,但我想知道是否有更简单的方法。
注意:只有与Python 2.7兼容的答案才对我有用;但如果差异相关,我会很高兴看到Python 3的答案。
def bigop(init, report):
data = init
while data < 10: # complicated condition
print 'working ...'
data += 1 # complicated operation
report(data)
def view(gen):
for value in gen:
print value # complicated display routine
def main(init):
"""
example:
>> main(7)
'working ...'
8
'working ...'
9
'working ...'
10
"""
pass
问题:如何定义main
?
答案 0 :(得分:1)
给出您的示例代码:
def main(init):
def report(x):
print x
bigop(init, report)
但是,我不认为这就是你在这里寻找的东西。大概您希望report
以某种方式将数据提供给view
。
你可以通过扭转局面来做到这一点 - 而不是view
是一个驱动另一个生成器的生成器,它是由外部调用者在其上调用send
驱动的生成器。例如:
def view():
while True:
value = yield
print value
def main(init):
v = view()
v.next()
def report(x):
v.send(x)
bigop(init, report)
但你说view
无法改变。当然,只要你viewdriver
yield
,就可以写send
view([data])
个新对象。或者,更简单地说,只需重复调用bigop
并让它迭代一个对象。
无论如何,我看不出你对此有何帮助。 bigop
不是协程,你不能把它变成一个协程。鉴于此,没有办法强迫它与其他协同程序合作共享。
如果要同时交错处理和报告,则必须使用线程(或进程)。并且“报告必须在BIGOP继续之前的每一步完成”这一事实已经成为您要求的一部分,这意味着您无论如何都无法安全地在此处做任何事情,所以我不确定您在寻找什么。
如果你只想交错处理和报告而不用并发 - 或定期挂钩到bigop
或其他类似的东西 - 你可以做一个coroutine,但它与使用子程序具有完全相同的效果 - 上面的两个例子非常相同。所以,你只是无缘无故地增加了复杂性。
(如果gevent
是I / O绑定的,您可以使用greenlet,并将I / O操作monkeypatch以将它们异步化,如eventlet
和viewdriver
。但是如果它是CPU这样做没有任何好处。)
阐述view([data])
想法:我上面所描述的内容相当于每次调用bigop
,所以它对你没有帮助。如果你想让它成为一个迭代器,你可以,但它会导致阻塞view
或转动class Reporter(object):
def __init__(self):
self.data_queue = []
self.viewer = view(self)
def __call__(self, data):
self.data_queue.append(data)
def __iter__(self):
return self
def __next__(self):
return self.data_queue.pop()
bigop(init, Reporter())
,因为你正在尝试向消费者提供消费者。
作为一个生成器可能很难理解,所以让我们把它构建成一个类:
bigop
每次report(data)
调用__call__
时,都会调用我们的view
,向队列中添加一个新元素。每次__next__
遍历循环时,它会调用我们的bigop
,从队列中弹出一个元素。如果view
保证比view
更快,那么一切都会有效,但第一次IndexError
提前,它会得到__next__
。
解决这个问题的唯一方法是让data_queue
尝试直到bigop
非空。但只是这样做会永远旋转,而不是让__next__
做生产新元素的工作。并且你不能将view
变成生成器,因为__call__
期望迭代器超过值,而不是迭代器而不是迭代器。
幸运的是,bigop
可以是一个生成器,因为__next__
并不关心它返回的值。所以,你可以扭转局面。但你不能这样做,因为那时没有什么可以驱动那个发电机。
因此,您必须在迭代下添加另一级协同程序。然后,next_coro
可以等待next
(通过调用call_coro
),这会产生__call__
,然后产生它获得的值。同时,send
必须call_coro
到同一next_coro
,等待它,然后屈服。
到目前为止,这并没有改变任何东西,因为你有两个例程都试图驱动__next__
,其中一个(next
)没有阻止其他地方,所以它是只是旋转它send(None)
来自__call__
的{{1}}看起来像next_coro
。
解决这个问题的唯一方法是建立一个蹦床(PEP 342包括一个通用蹦床的来源,虽然在这种情况下你可以建立一个更简单的专用蹦床),安排call_coro
并next_coro
明确地替换,确保run
正确处理两个不同入口点之间的交替,然后从__next__
(和__init__
)驱动调度程序的{{1}}。
困惑?在本周的一集之后你不会是......不,我在开玩笑。你会感到困惑。写所有这些都是一回事;调试它是另一回事。 (特别是因为每一个重要的堆栈跟踪都会立即终止于蹦床。)那么有什么用呢?与使用greenlets或线程完全相同的好处,具有完全相同的缺点。
由于你的原始问题是否比使用线程更简单,答案是:不,没有。