通过在 Python 中等待协程来设置类属性

Posted

技术标签:

【中文标题】通过在 Python 中等待协程来设置类属性【英文标题】:Set class attribute by awaiting a coroutine in Python 【发布时间】:2020-12-09 14:52:42 【问题描述】:

我有一个类,它的属性包含一个 Redis 连接,如下所示:

import redis

class RedisService:
    db = redis.Redis(host=RedisConfig.REDIS_HOST, port=RedisConfig.REDIS_PORT)

    @staticmethod
    def exists(key):
        return RedisService.db.exists(key)

这很好用。但现在我想用异步替代品替换它:

import aioredis

class RedisService:
    db = await aioredis.create_connection((RedisConfig.REDIS_HOST, RedisConfig.REDIS_PORT))

    @staticmethod
    async def exists(key):
        value = await RedisService.db.execute('GET', key)
        return value

await 不允许为类属性赋值。如果我删除 await,当我调用 RedisService.exists() 时,我会得到:

File "./src/service/redis.py", line 12, in exists
     value = await RedisService.db.execute('GET', key)
 AttributeError: 'coroutine' object has no attribute 'execute'

那么如何通过等待协程给类属性赋值呢?

请注意,我直接调用await RedisService.exists() 而不创建对象实例。它应该是一个静态方法,并且由于某些 BL 原因应该在不创建实例的情况下调用。

【问题讨论】:

【参考方案1】:

您可以在模块中创建一个init函数来初始化所需的类属性:

# let's assume module name redis_service.py

class RedisService:
    ...

async def init():
    RedisService.db = await aioredis.create_connection(...)

您可以从 main 入口点调用此函数:

import redis_service
...

async def main():
    await redis_service.init()
    ...

if __name__ == '__main__':
    asyncio.run(main())

这具有能够重新初始化“全局”异步数据的优势,以防您多次运行asyncio.run()。此外,aioredis.create_connection() 返回的对象很可能与当前的事件循环相关联,因此在类定义时执行它会阻止使用 asyncio.run

【讨论】:

不错的主意,但我不想在课堂上初始化(或创建对象)。我需要立即调用该类的静态方法,并且该静态方法使用其属性。喜欢RedisService.get_key()。如果有初始化,那么网络上有一些方法使用元类和__init__ 构造函数来实现此目的。所以我更喜欢它们。 @iedmrc 为什么需要立即调用静态方法?如果你在***而不是async def,你怎么能做到这一点?使用元类在加载时强制初始化听起来会使该方法与asyncio.run 不兼容,恕我直言,这是个坏主意。 这很像我们出于某些 BL 原因在这里使用类的方式。我认为这不是我们应该考虑的。 IMO,必须有一种通过调用异步函数来设置类属性的简洁方法,就像调用同步函数一样简单。就像:an_attr = await a_func()。使用同步函数而不是异步函数进行这样的分配真的很有趣。如果我们不能,为什么我们有await :)。好的,我知道我们并没有认为没有这样的东西不好,但我希望有一种更相似的方式来实现这一点。但在这里你提出了一种更复杂的方法。顺便说一句,非常感谢! @iedmrc 同步代码和异步代码之间的区别在于异步必须由事件循环驱动。这就是为什么 awaits 必须在异步函数中,而你不能只在顶层等待东西。如果您坚持在类定义中等待,也许您可​​以将类定义放在async def 中,并在完成后导出它们。但这将再次需要init 样式的函数。除了我在答案中已经写的内容之外,我认为我不能真正帮助你,但也许其他人可以 - 祝你好运! @iedmrc 其中一些应该在本地工作,但是当async.run 创建不同的事件循环时会失败。你试图做的当然不是好的做法,也是一个坏主意,除非你真的知道你在做什么,这(没有冒犯)在这里似乎不是这样。这个答案提供了一个强大的解决方案,无论您如何启动事件循环,它都可以工作。 (它仍然不适用于同时并发的事件循环,但这是基于类属性的设计的一个限制,并且不是 asyncio 开始时的重要考虑因素。)

以上是关于通过在 Python 中等待协程来设置类属性的主要内容,如果未能解决你的问题,请参考以下文章

爬虫—使用协程构建高性能爬虫

爬虫速度太慢?来试试用异步协程提速吧!

Python 获取类属性

Python - 在对象实例化期间设置类属性(构造)

python 属性,类方法和静态方法

Python中父类和子类间类属性(非实例属性)的设置获取的传递