【问题标题】:Unable to Yield From inside async function and get yielded data无法从异步函数内部产生并获取产生的数据
【发布时间】:2019-05-25 05:19:25
【问题描述】:

我正在尝试从异步函数中的函数中获取收益。花了几个小时试图弄清楚这一点并遍历 Stack Overflow 以找到以前回答但无法帮助我找到解决自己问题的类似问题,我发现自己在这里。

很简单,我想使用 Web 浏览器和 Websockets 通过 Panoramisk 查询 Asterisk 管理界面。当用户连接到 websocket 服务器时,它会运行 ws_handle 方法

async def ws_handle(websocket, path):
    await register(websocket)
    try:
        async for message in websocket:
            data = json.loads(message)
            ...

然后我想检索一些数据,然后交付给客户端。我遇到的问题是我发现我无法说出来

exts = yield from ExtensionStateList.get(AmiManager)

ExtensionStateList.get 函数的位置(大致)如下:

def get(AmiManager):
    queues_details = yield from AmiManager.send_action(
        {'Action': 'ExtensionStateList'})

    ...

    val = {
        'extensions': EXTENSIONS,
        'parks': PARKS,
        'paging': PAGING,
        'confrences': CONFRENCES,
        'apps': APPS,
        'misc': MISC
    }

    return val

我在另一个与我的 websockets 服务器文件分开的测试文件中使用了同一个文件 ExtensionStateList.py,以非异步方法调用它,如前所示

exts = yield from ExtensionStateList.get(AmiManager)

没有问题,它使用函数返回的值填充 exts。

我的研究让我像这样迭代它:

async for a in ExtensionStateList.get(AmiManager):
    yield a

但我不知道如何使用它来填充我希望填充的变量。我试过这样:

exts = ''
async for a in ExtensionStatList.get(AmiManager):
    exts = exts+a

只是被告知它不能将 AsyncIO.Future 加入到字符串中。我还尝试将return val 换成yield val,但还是没有成功。

显然,对我来说,这是我缺乏 Python 知识的一个缺点。我能做些什么?我在想也许我可以将 ExtensionStateList.get 更改为 async,但这会让我回到我现在所处的同一条船上?

额外

我继续翻遍StackOverflow,发现如下问题:

What is the difference between @types.coroutine and @asyncio.coroutine decorators?

在我看来,也许如果我在ws_handle 上方的行上添加@asyncio.coroutine,就像这样:

@asyncio.coroutine
async def ws_handle(websocket, path):

我将能够:

exts = yield from ExtensionStateList.get(AmiManager)

但是,我发现这不起作用,它告诉我无法从异步函数内部产生。我是否误解了我在这里阅读的内容?或者我可能没有正确实施它?我在这方面走在正确的轨道上吗?

根据这里给出的答案:

'yield from' inside async function Python 3.6.5 aiohttp

我也尝试过像这样等待函数:

exts = await ExtensionStateList.get(AmiManager)

但是,Python 告诉我对象生成器不能用于 await 表达式。

更多

对于那些可能感兴趣的人,这就是我调用 ws_handle 函数的方式。它在创建 websocket 服务器时被调用,websocket 服务器负责调度/调用? ws_handle 函数。

在我看来,它为每个连接的客户端调用一次这个函数,这个函数一直运行到用户断开连接。

WebsocketServer = websockets.serve(ws_handle, host, port)
asyncio.get_event_loop().run_until_complete(WebsocketServer)
asyncio.get_event_loop().run_forever()

附录

是的,我再次添加更多内容。我已经修改了我的 ExtensionStateList.py,以便在调用 get 方法时,它按以下方式执行:

async def get(AmiManager):
    val = await getInternal(AmiManager)
    return val

@asyncio.coroutine
def getInternal(AmiManager):

我现在可以在 getInternal 函数内部使用yield from,这以前是我的 get 函数,我可以调用它并按以下方式接收日期:

exts = await ExtensionStateList.get(AmiManager)

我想我已经掌握了这一点,并且我看到了它们是如何以两种不同的方式来做几乎相同的事情。

感谢你们为我指明了正确的方向!

【问题讨论】:

  • AmiManager 中有什么异步的吗?如果没有,只需将其用作常规(非异步)生成器。但是,如果它正在执行任何 I/O,您可能需要查看 run_in_executor 之类的内容,以防止它阻塞其他 I/O。
  • 是的,AmiManager 指的是 Panoramisk(如果你是农家的话)。它确实是异步运行的,我必须使用 yield from 来检索结果。作为参考,我将在我的问题中添加更多关于我如何调用循环的细节。

标签: python python-3.x websocket python-asyncio yield-from


【解决方案1】:

你对 asyncio 语法的变化有点困惑。在 3.5 中,我们转向了 async def 和 await。 See this answer for details 并注意以下示例:

你想要一个异步生成器。请参阅Pep 525 和示例代码:

async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

【讨论】:

  • 好的,所以如果我要让我的 ExtensionStateList.get 成为异步函数,并且在我的 ws_handle 函数中我要拥有 exts = ExtensionStateList.get...await exts,我知道这将是正确的方法这样做呢?如果是这种情况,不幸的是,我正在使用的库 (Panoramisk) 明确需要 yield from 才能将命令发送到 AMI 接口并检索数据,这让我回到了以前的同一条船上。但这会是预期的现代语法吗?它应该以其他方式工作吗?谢谢。
  • github.com/gawel/panoramisk/issues/67 - 看起来该库已针对 python 3.7 支持进行了更新。如果 await 语法还有问题,写个 issue?
  • Hmmmm :S 我会试试的,我只是在关注他们的文档,但不知道它会支持新语法。我会试一试并分享我的进展。
【解决方案2】:

别想了——我没弄明白。根据我在下面的评论,我一定是累了,误以为程序工作出错了。哎呀。

尤里卡!

经过几个小时的头发撕裂、文档阅读和几乎眼泪,我发现@async.coroutine 需要像这样应用于ExtensionStateList.get 方法:

#In ExtensionStateList.py
@asyncio.coroutine
def get(AmiManager):

并且我必须在这样调用该命令时等待:

exts = ExtensionStateList.get(AmiManager)
await exts

我发现如果我只 await exts 而不使其成为 asyncio.coroutine,它将返回错误,指出您不能等待生成器。

【讨论】:

  • 这不是给你一个生成器。 yield from 是在 3.5 中添加 asyncawait 之前等待协程的方式。您找到了一种有效的语法,但这不是您要问的。
  • 好的,这给了我预期的结果,但是有没有更好的方法来做到这一点,其中语法更现代?作为参考,我打算使用预装的 Python 3.6 在 FreePBX 14 上运行它。我对针对旧版本的 Python 没有兴趣。
  • 此外,事实证明它实际上不起作用,并且出现错误。认为它起作用是我的错 - 我责怪疲劳。它抛出错误并将我试图检索的数据打印到控制台,而不是将其发送到客户端,我一定以为我打算将它打印到屏幕上并且我没有正确阅读。好尴尬。
猜你喜欢
  • 1970-01-01
  • 2016-02-13
  • 2019-10-21
  • 1970-01-01
  • 2016-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多