Предварительный выбор генератора Python? - PullRequest
5 голосов
/ 06 сентября 2011

У меня есть генератор, который занимает много времени для запуска каждой итерации.Есть ли стандартный способ получить значение, а затем сгенерировать следующее значение, ожидая повторного вызова?

Генератор вызывается при каждом нажатии кнопки в графическом интерфейсе, и пользователь должен будет учитывать результат после каждого нажатия кнопки.

РЕДАКТИРОВАТЬ: обходной путь может быть:*

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

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

Ответы [ 4 ]

7 голосов
/ 06 сентября 2011

Нет.Генератор не асинхронный.Это не многопроцессорность.

Если вы хотите избежать ожидания вычислений, вам следует использовать пакет multiprocessing, чтобы независимый процесс мог выполнить ваши дорогостоящие вычисления.

Вам нужен отдельный процесс, который вычисляет иставить в очередь результаты.

Ваш «генератор» может затем просто удалить имеющиеся результаты из очереди.

6 голосов
/ 06 сентября 2011

Если бы я хотел сделать что-то вроде твоего обходного пути, я написал бы такой класс:

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

Это будет работать отдельно от вашего основного приложения. Ваш графический интерфейс должен реагировать независимо от того, сколько времени занимает выборка каждой итерации.

1 голос
/ 06 сентября 2011

Вы определенно можете сделать это с генераторами, просто создайте свой генератор так, чтобы каждый вызов 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 для предварительной выборки.

0 голосов
/ 16 июля 2012

Я был после чего-то похожего. Я хотел, чтобы 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...