通过在 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 中等待协程来设置类属性的主要内容,如果未能解决你的问题,请参考以下文章