如何向handle_client协程传递额外参数?

12

使用asyncio作为套接字服务器的推荐方法是:

import asyncio

async def handle_client(reader, writer):
    request = (await reader.read(100)).decode()
    response = "Data received." 
    writer.write(response.encode())

async def main():
    loop.create_task(asyncio.start_server(handle_client, 'localhost', 15555))

loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()

这个工作很好,但现在我需要接收适当的客户端请求,然后使用aiohttp库从第三方restful API获取数据。

这需要创建一个会话变量,如下所示:

from aiohttp import ClientSession

session = ClientSession()

但这也应该放在协程内部,所以我会把它放在主函数里:

但是这也应该在一个协程中,所以我将把它放在主函数中:

async def main():
    session = ClientSession()
    loop.create_task(asyncio.start_server(handle_client, '', 55555))
现在我需要将会话变量传递给aiohttp get协程来获取剩余的API数据。

现在我需要将会话变量传递给aiohttp get协程来获取剩余的API数据:

async with session.get(url, params=params) as r:
    try:
        return await r.json(content_type='application/json')
    except aiohttp.client_exceptions.ClientResponseError:
        ....

我的问题是,如果handle_client协程坚持仅具有reader、writer参数,并且全局变量对我没有帮助,因为会话必须存在于协程内部,那么如何将会话变量传递给handle_client协程?

1个回答

22

您可以使用临时函数或lambda函数:

async def main():
    session = aiohttp.ClientSession()
    await asyncio.start_server(lambda r, w: handle_client(r, w, session),
                               '', 55555)

这个可以行得通是因为虽然lambda不是技术上的协程,但它的行为方式与协程非常相似——当被调用时,它返回一个协程对象

对于更大型的程序,您可能更喜欢基于类的方法,在这种方法中,一个类封装了多个客户端共享的状态,而无需显式地传递它。例如:

class ClientContext:
    def __init__(self):
        self.session = aiohttp.ClientSession()
        # ... add anything else you will need "globally"

    async def handle_client(self, reader, writer):
        # ... here you get reader and writer, but also have
        # session etc as self.session ...

async def main():
    ctx = ClientContext()
    await asyncio.start_server(ctx.handle_client), '', 55555)

谢谢回复。我找不到任何Python文档证明支持lambda函数的协程。然而,令我惊讶的是,当您将返回命名协程的lambda作为参数传递给start_server时,代码实际上可以工作!但是,我对您答案的第二部分表示怀疑,因为会话变量应该只存在于协程内部... - jsstuball
@JSStuball 在 start_server 的情况下,明确记录了它的回调可以是一个普通的回调函数。但在这种情况下,lambda 调用协程(这是合法的 - 你会得到协程对象)并返回结果协程对象。这使其在功能上等同于协程函数,并且它可以正常工作。当然,你是正确的,lambda 不是一个真正的协程,例如它不能包含 await - user4815162342
@JSStuball 我已经修复了类示例中的拼写错误,在其中 handle_client 也应该是一个协程。 - user4815162342
@JSStuball 我在 main() 协程中创建它,就像你的代码一样。创建的实例被传递ClientContext 构造函数,但这并不重要。 - user4815162342
哦,是的,我的错。你说得对。 - jsstuball
1
@JSStuball 我其实还没有检查过,但我怀疑 ClientSession 只关心在创建它时是否有 某个 协程正在运行(代码似乎证实了这一点)。换句话说,即使在 ClientContext.__init__ 中实例化 ClientSession,也不会有任何警告。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接