Python生成器预取?

Python生成器预取?,python,generator,Python,Generator,我有一个生成器,每次迭代运行都需要很长时间。是否有一种标准方法让它生成一个值,然后在等待再次调用时生成下一个值 在GUI中每次按下一个按钮时都会调用生成器,用户在每个按钮按下之后都会考虑结果。 编辑:解决方法可能是: def initialize(): res = next.gen() def btn_callback() display(res) res = next.gen() if not res: return 不,发电机不是异步的。这不

我有一个生成器,每次迭代运行都需要很长时间。是否有一种标准方法让它生成一个值,然后在等待再次调用时生成下一个值

在GUI中每次按下一个按钮时都会调用生成器,用户在每个按钮按下之后都会考虑结果。 编辑:解决方法可能是:

def initialize():
    res = next.gen()

def btn_callback()
    display(res)
    res = next.gen()
    if not res:
       return

不,发电机不是异步的。这不是多重处理

如果希望避免等待计算,则应使用
多处理
包,以便独立进程可以执行昂贵的计算

您需要一个单独的进程来计算和排队结果


然后,您的“生成器”可以简单地将可用结果出列。

您完全可以使用生成器来完成此操作,只需创建生成器,使每个
next
调用在获取下一个值和通过输入多个
yield
语句返回它之间交替进行。以下是一个例子:

import itertools, time

def quick_gen():
    counter = itertools.count().next
    def long_running_func():
        time.sleep(2)
        return counter()
    while True:
        x = long_running_func()
        yield
        yield x

>>> itr = quick_gen()
>>> itr.next()   # setup call, takes two seconds
>>> itr.next()   # returns immediately
0
>>> itr.next()   # setup call, takes two seconds
>>> itr.next()   # returns immediately
1

请注意,生成器不会自动进行处理以获取下一个值,而是由调用方为每个值调用两次
next
。对于您的用例,您可以调用
next
一次作为设置,然后每次用户单击按钮时,您都会显示生成的下一个值,然后再次调用
next
进行预取。

如果我想做类似于您的解决方法的事情,我会编写如下类:

class PrefetchedGenerator(object):
    def __init__(self, generator):
         self._data = generator.next()
         self._generator = generator
         self._ready = True

    def next(self):
        if not self._ready:
            self.prefetch()
        self._ready = False
        return self._data

    def prefetch(self):
        if not self._ready:
            self._data = self._generator.next()
            self._ready = True
它比你的版本更复杂,因为我这样做是为了让它处理不调用预取或调用预取的次数过多。基本思想是,当需要下一项时,调用.next()。当你有“时间”消磨时,你调用预回迁

另一个选项是线程

class BackgroundGenerator(threading.Thread):
    def __init__(self, generator):
        threading.Thread.__init__(self)
        self.queue = Queue.Queue(1)
        self.generator = generator
        self.daemon = True
        self.start()

    def run(self):
        for item in self.generator:
            self.queue.put(item)
        self.queue.put(None)

    def next(self):
            next_item = self.queue.get()
            if next_item is None:
                 raise StopIteration
            return next_item

这将与主应用程序分开运行。无论获取每个迭代需要多长时间,您的GUI都应该保持响应性。

我想要的是类似的东西。我想让yield在后台线程处理next,next时快速返回一个值(如果可以的话)

import Queue
import time
import threading

class MyGen():
    def __init__(self):
        self.queue = Queue.Queue()
        # Put a first element into the queue, and initialize our thread
        self.i = 1
        self.t = threading.Thread(target=self.worker, args=(self.queue, self.i))
        self.t.start()

    def __iter__(self):
        return self

    def worker(self, queue, i):
        time.sleep(1) # Take a while to process
        queue.put(i**2)

    def __del__(self):
        self.stop()

    def stop(self):
        while True: # Flush the queue
            try:
                self.queue.get(False)
            except Queue.Empty:
                break
        self.t.join()

    def next(self):
        # Start a thread to compute the next next.
        self.t.join()
        self.i += 1
        self.t = threading.Thread(target=self.worker, args=(self.queue, self.i))
        self.t.start()

        # Now deliver the already-queued element
        while True:
            try:
                print "request at", time.time()
                obj = self.queue.get(False)
                self.queue.task_done()
                return obj
            except Queue.Empty:
                pass
            time.sleep(.001)

if __name__ == '__main__':
    f = MyGen()
    for i in range(5):
#        time.sleep(2) # Comment out to get items as they are ready
        print "*********"
        print f.next()
        print "returned at", time.time()
上述代码给出了以下结果:

*********
request at 1342462505.96
1
returned at 1342462505.96
*********
request at 1342462506.96
4
returned at 1342462506.96
*********
request at 1342462507.96
9
returned at 1342462507.96
*********
request at 1342462508.96
16
returned at 1342462508.96
*********
request at 1342462509.96
25
returned at 1342462509.96


一种比多重处理更简单的方法可能是在开始时将一个结果排队,然后在每个按钮上按“显示排队的结果”并调用生成器将下一个结果排队。我希望有一个更优雅的解决方案。@foosion:它仍然是完全同步的。生成前两个需要两倍于生成第一个的时间。之后每一步的时间都是一样的。@S.洛特:如果工作是在用户研究之前的结果时完成的,并且一按下按钮就会弹出一个新的结果,那么用户不会注意到任何延迟。如果工作是在用户按下按钮后完成的,则用户会坐在那里等待结果出现。@foosion:它仍然是完全同步的。如果你想在用户思考的时候做一些事情,你需要使用一个单独的进程(或者有时是一个单独的线程)。问题是:为什么要花这么长时间来创建生成器?解决此问题的最佳方法取决于此。@Winston,在我正在处理的情况下,它从磁盘读取一个图像文件并进行一些处理,但我也对一般问题感兴趣。但在调用display()后,您的应用程序将立即失去响应。这真的能让你满意地解决问题吗?@Winston,它在处理过程中的某个时刻必须是无响应的。在用户查看结果时不响应似乎比在用户等待下一个结果时更好,但事实并非如此。如果您在线程或其他进程中运行任务,它不会变得无响应。与其调用两次,不如参阅我的变通方法编辑,了解可能更简单的方法现在他需要调用生成器两次,每过一秒,gnertor就会像以前一样长时间地阻塞——没有任何改进,除非他在阻塞时有更大的灵活性。阻塞时间的灵活性正是OP所要求的,单击按钮后立即返回,然后进行处理。def init后面是否应该是:threading.Thread.\uu init\uU_Iu(self)我试图通过添加:gen=BackgroundGenerator(generator()),然后调用next(gen,default)来使用它,得到的TypeError BackgroundGenerator对象不是迭代器。我做错了什么?@foosion,我把BackgroundGenerator变成了一个iterable,而不是迭代器。我把它改成了迭代器。(这一次我也测试了它)这似乎工作得很好,除了当我试图退出程序时,程序挂起。我使用tkinter作为gui,只是调用它的quit函数退出。有什么立即浮现在脑海中的吗?@foosion,将
self.daemon=True
添加到线程构造函数中。默认情况下,python的运行时间与任何线程的运行时间一样长,而且我猜您不会耗尽迭代器,因此线程永远不会结束。将线程设置为守护进程会告诉python即使该线程尚未完成也可以退出。