Python生成器预取?

时间:2011-09-06 17:20:10

标签: python generator

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

每次在gui中按下按钮时都会调用生成器,并且每次按下按钮后都会要求用户考虑结果。

编辑:解决方法可能是:

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

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

4 个答案:

答案 0 :(得分:7)

没有。生成器不是异步的。这不是多处理。

如果您想避免等待计算,您应该使用multiprocessing包,以便独立的流程可以进行昂贵的计算。

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

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

答案 1 :(得分:6)

如果我想做一些像你的解决方法,我会写一个这样的课:

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

它比你的版本更复杂,因为我这样做是因为它处理不会调用prefetch或调用prefetch太多次。基本的想法是,当你想要下一个项目时,你可以调用.next()。当你有“时间”杀死时,你可以调用prefetch。

你的另一个选择是一个线程..

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都应保持响应。

答案 2 :(得分:1)

你绝对可以使用生成器执行此操作,只需创建生成器,以便每个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进行预取。 / p>

答案 3 :(得分:0)

我是在追求类似的东西。我希望yield能够快速返回一个值(如果可能的话),而后台线程处理下一个,下一个。

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