Python-генератор 'yield' в отдельной функции - PullRequest
1 голос
/ 01 сентября 2011

Я реализую библиотеку утилит, которая является своего рода диспетчером задач, предназначенным для работы в распределенной среде службы облачных вычислений Google App Engine. (Он использует комбинацию очередей задач и memcache для выполнения фоновой обработки). Я планирую использовать генераторы для управления выполнением задач, по сути, обеспечивая непревзойденный «параллелизм» с помощью yield в коде пользователя.

Тривиальный пример - обработка группы объектов базы данных - может выглядеть примерно так:

class EntityWorker(Worker):
    def setup():
        self.entity_query = Entity.all()
    def run():
        for e in self.entity_query:
            do_something_with(e)
            yield

Как мы знаем, yield - это двусторонний канал связи, позволяющий передавать значения в код, который использует генераторы. Это позволяет имитировать «вытесняющий API», такой как вызов SLEEP ниже:

def run():
    for e in self.entity_query:
        do_something_with(e)
        yield Worker.SLEEP, timedelta(seconds=1)

Но это безобразно. Было бы здорово спрятать yield внутри отдельной функции, которая может быть вызвана простым способом:

self.sleep(timedelta(seconds=1))

Проблема заключается в том, что включение yield в функцию sleep превращает it в функцию генератора. Поэтому вызов выше просто вернет другой генератор. Только после добавления .next() и yield обратно мы получим предыдущий результат:

yield self.sleep(timedelta(seconds=1)).next()

что, конечно, еще более безобразно и излишне многословно, чем раньше.

Отсюда мой вопрос: есть ли способ поместить yield в функцию, не превращая ее в функцию генератора, но используя ее другими генераторами для получения вычисляемых ею значений?

Ответы [ 3 ]

3 голосов
/ 01 сентября 2011

Вы, похоже, упускаете очевидное:

class EntityWorker(Worker):
    def setup(self):
        self.entity_query = Entity.all()

    def run(self):
        for e in self.entity_query:
            do_something_with(e)
            yield self.sleep(timedelta(seconds=1))

    def sleep(self, wait):
        return Worker.SLEEP, wait

Это yield, который превращает функции в генераторы, невозможно его исключить.

Чтобы скрыть нужную вам доходностьфункция более высокого порядка, в вашем примере это map:

from itertools import imap

def slowmap(f, sleep, *iters):
    for row in imap(f, self.entity_query):
        yield Worker.SLEEP, wait

def run():
    return slowmap(do_something_with, 
                   (Worker.SLEEP, timedelta(seconds=1)),
                   self.entity_query)
2 голосов
/ 01 сентября 2011

Увы, это не сработает. Но «средний путь» может подойти:

def sleepjob(*a, **k):
    if a:
        return Worker.SLEEP, a[0]
    else:
        return Worker.SLEEP, timedelta(**k)

So

yield self.sleepjob(timedelta(seconds=1))
yield self.sleepjob(seconds=1)

выглядит нормально для меня.

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

Я бы посоветовал вам взглянуть на ndb . Он использует генераторы в качестве сопрограмм (как вы предлагаете здесь), что позволяет вам писать программы, которые работают с rpcs асинхронно.

API делает это, оборачивая генератор другой функцией, которая «заполняет» генератор (он вызывает .next () немедленно, так что код начинает выполнение). Тасклеты также предназначены для работы с инфраструктурой rpc App Engine, что позволяет использовать любые существующие асинхронные вызовы API.

Используя модель параллелизма, используемую в ndb, вы yield либо будущий объект (аналогичный описанному в pep-3148), либо объект rpc App Engine. Когда этот rpc завершен, выполнение функции, получившей объект, может продолжаться.

Если вы используете модель, основанную на ndb.model.Model, то следующее позволит вам асинхронно выполнять итерацию по запросу:

from ndb import tasklets

@tasklets.tasklet
def run():
it = iter(Entity.query())
# Other tasklets will be allowed to run if the next call has to wait for an rpc.
while (yield it.has_next_async()):
  entity = it.next()
  do_something_with(entity)

Несмотря на то, что ndb все еще считается экспериментальным (некоторые из его кода обработки ошибок все еще требуют некоторой работы), я бы порекомендовал вам взглянуть на него. Я использовал его в последних двух проектах и ​​нашел, что это отличная библиотека.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...