在Python中将回调转换为生成器?

时间:2015-04-19 00:01:43

标签: python callback generator coroutine

假设我们有一些库(例如用于XML解析)接受回调并在每次遇到某些事件时调用它(例如,找到一些XML标记)。我希望能够将这些回调转换为可以通过for循环迭代的生成器。如果没有使用threads或收集所有回调结果(即使用延迟评估),这在Python中是否可行?

示例:

# this is how I can produce the items
def callback(item)
    # do something with each item
parser.parse(xml_file, callback=callback)

# this is how the items should be consumed
for item in iter_parse(xml_file):
    print(item)

我试图研究是否可以使用协同程序,但似乎协同程序对于从生产者推送数据很有用,而生成器将数据提取给消费者。

自然的想法是生产者和消费者会成为协同执行流程的协同程序。

我设法让生产者 - 消费者模式使用asyncio循环(与此answer类似)。但它不能像for循环中的生成器一样使用:

import asyncio

q = asyncio.Queue(maxsize=1)

@asyncio.coroutine
def produce(data):
    for v in data:
        print("Producing:", v)
        yield from q.put(v)
        print("Producer waiting")
    yield from q.put(None)
    print("Producer done")

@asyncio.coroutine
def consume():
    while True:
        print("Consumer waiting")
        value = yield from q.get()
        print("Consumed:", value)
        if value is not None:
            # process the value
            yield from asyncio.sleep(0.5)
        else:
            break
    print("Consumer done")

tasks = [
    asyncio.Task(consume()),
    asyncio.Task(produce(data=range(5)))
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

问题是结果不能在for循环中迭代,因为它是由循环管理的。

当我重写代码以便从普通函数调用回调时,问题是从回调中调用的asyncio.Queue.put()不会阻塞并且计算不是懒惰的。

import asyncio

q = asyncio.Queue(maxsize=1)

def parse(data, callback):
    for value in data:
        # yield from q.put(value)
        callback(value)

@asyncio.coroutine
def produce(data):
    @asyncio.coroutine
    def enqueue(value):
        print('enqueue()', value)
        yield from q.put(value)
    def callback(value):
        print('callback()', value)
        asyncio.async(enqueue(value))
    parse(data, callback)

    print('produce()')
    print('produce(): enqueuing sentinel value')
    asyncio.async(enqueue(None))
    print('produce(): done')

@asyncio.coroutine
def consume():
    print('consume()')
    while True:
        print('consume(): waiting')
        value = yield from q.get()
        print('consumed:', value)
        if value is not None:
            # here we'd like to yield and use this in a for loop elsewhere
            print(value)
        else:
            break
    print('consume(): done')

tasks = [
    asyncio.Task(consume()),
    asyncio.Task(produce(range(5)))
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

# I'd like:
# for value in iter_parse(data=range(5)):
#   print('consumed:', value)

甚至可以使用asyncio进行这种计算,还是需要使用greenlet或gevent?我在for循环中似乎in gevent it is possible to iterate over async results但是如果可能的话我不喜欢依赖另一个库而且它还没有为Python 3做好准备。

0 个答案:

没有答案