【问题标题】:Python - waiting for asyncio task in non-async __init__ of certain classPython - 在某个类的非异步 __init__ 中等待异步任务
【发布时间】:2021-12-09 04:02:28
【问题描述】:

我正在创建一个带有__init__ 函数和另一个函数的python 类,比如说f__init__ 函数不是异步的,但另一个函数是。现在,我正在尝试从__init__ 执行另一个函数。

我研究过的东西:

  • 我找到了一些带有 get_event_loop/get_running_loop 和 run_until_complete 的代码,但这会给出一个 RuntimeError,表明事件循环已经在运行。
  • 我看过关于nest_asyncio的cmets,但是好像有点脏。
  • 此外,我使用 create_task、gather 和 wait 尝试了几件事。但在这种情况下,代码不会等到任务完全执行。
  • 我可以先调用构造函数,然后使用 await 调用第二个函数来拆分它,但我希望在构造函数期间调用第二个函数。

请注意,这是在 Azure Function App 上下文中。

一些示例代码:

import asyncio
class myClass(object):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
        #asyncio.create_task(self.f('abc'))
        self.loop = asyncio.get_event_loop()
        self.loop.run_until_complete(self.f('abc'))
    
    async def f(self, x):
        await asyncio.sleep(1)
        self.x = x
        
if __name__ == '__main__':
    cl = myClass(1,2)
    print(cl.x)

这在这个简单的上下文中可以正常工作,但在 Azure Functions 中这样的更复杂的上下文中却不行,我试图在函数中使用 myClass,然后想使用它的“x”属性。

【问题讨论】:

  • 你真的需要从__init__调用异步函数吗?您可能希望在初始化后运行它或提供异步 classmethod 来运行异步代码,然后将结果传递给构造函数

标签: python python-asyncio


【解决方案1】:

__init__ 中的代码不能使用 await,因此您需要做 2 件事中的 1 件事。

__init__ 中启动一些异步任务并提供第二种方法来等待它们:

import asyncio
class myClass(object):
    """ must await on obj.finish_init() before the object can be used """
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
        self._finish_init_promise = asyncio.create_task(self.f('abc'))
        #self.loop = asyncio.get_event_loop()
        #self.loop.run_until_complete(self.f('abc'))
    async def finish_init(self):
        await self._finish_init_promise
        return self
    
    async def f(self, x):
        await asyncio.sleep(1)
        self.x = x
async def main():
    cl = await myClass(1,2).finish_init()
    print(cl.x)
if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

但是,可能不希望构造一个不完整的对象(您可能希望有一个布尔值来指示它是否已准备好),因此另一种方法是提供一个异步 classmethod 来准备数据,然后传递它给构造函数的数据:

import asyncio
class myClass(object):
    def __init__(self, arg1, arg2, x):
        self.arg1 = arg1
        self.arg2 = arg2
        self.x = x
    @classmethod
    async def make(cls, arg1, arg2):
        x = await cls.obvious_example_will_be_replaced_with_real_use_case('abc')
        return cls(arg1, arg2, x)
    @staticmethod
    async def obvious_example_will_be_replaced_with_real_use_case(x):
        await asyncio.sleep(1)
        return x
async def main():
    cl = await myClass.make(1,2)
    print(cl.x)
if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

【讨论】:

  • 好的,好像 enter 没有在评论框中换行,很高兴知道。无论如何,两个很好的建议!关于第二种选择的问题:“make”是否必须是一个类方法,为什么?另外,在“make”函数中,不应该等待 cls.obvious...吗?
  • 你说得非常正确,应该有x = await ... 我已经修复了。 make 不能是常规方法,因为必须在构造实例之前调用它,全局函数会很奇怪,因为它与类非常相关。使用staticmethod 并通过名称引用类也可以,但是classmethod 意味着子类可以使用make 来实例化子类,从而提供您可能不需要的更多可扩展性。
猜你喜欢
  • 2014-09-06
  • 2014-03-18
  • 1970-01-01
  • 2013-02-10
  • 2017-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-24
相关资源
最近更新 更多