可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How can I define a class with await
in the constructor or class body?
For example what I want:
import asyncio # some code class Foo(object): async def __init__(self, settings): self.settings = settings self.pool = await create_pool(dsn) foo = Foo(settings) # it raises: # TypeError: __init__() should return None, not 'coroutine'
or example with class body attribute:
class Foo(object): self.pool = await create_pool(dsn) # Sure it raises syntax Error def __init__(self, settings): self.settings = settings foo = Foo(settings)
My solution (But I would like to see a more elegant way)
class Foo(object): def __init__(self, settings): self.settings = settings async def init(self): self.pool = await create_pool(dsn) foo = Foo(settings) await foo.init()
回答1:
Most magic methods aren't designed to work with async def
/await
- in general, you should only be using await
inside the dedicated asynchronous magic methods - __aiter__
, __anext__
, __aenter__
, and __aexit__
. Using it inside other magic methods either won't work at all (as is the case with __init__
), or will force you to always use whatever triggers the magic method call in an asynchronous context.
Existing asyncio
libraries tend to deal with this in one of two ways: First, I've seen the factory pattern used (asyncio-redis
, for example):
import asyncio dsn = "..." class Foo(object): @classmethod async def create(cls, settings): self = Foo() self.settings = settings self.pool = await create_pool(dsn) return self async def main(settings): settings = "..." foo = await Foo.create(settings)
Other libraries use a top-level coroutine function that creates the object, rather than a factory method:
import asyncio dsn = "..." async def create_foo(settings): foo = Foo(settings) await foo._init() return foo class Foo(object): def __init__(self, settings): self.settings = settings async def _init(self): self.pool = await create_pool(dsn) async def main(): settings = "..." foo = await create_foo(settings)
The create_pool
function from aiopg
that you want to call in __init__
is actually using this exact pattern.
This at least addresses the __init__
issue. I haven't seen class variables that make asynchronous calls in the wild that I can recall, so I don't know that any well-established patterns have emerged.
回答2:
I would recommend a separate factory method. It's safe and straightforward. However, if you insist on a async
version of __init__()
, here's an example:
def asyncinit(cls): __new__ = cls.__new__ async def init(obj, *arg, **kwarg): await obj.__init__(*arg, **kwarg) return obj def new(cls, *arg, **kwarg): obj = __new__(cls, *arg, **kwarg) coro = init(obj, *arg, **kwarg) #coro.__init__ = lambda *_1, **_2: None return coro cls.__new__ = new return cls
Usage:
@asyncinit class Foo(object): def __new__(cls): '''Do nothing. Just for test purpose.''' print(cls) return super().__new__(cls) async def __init__(self): self.initialized = True
async def f(): print((await Foo()).initialized) loop = asyncio.get_event_loop() loop.run_until_complete(f())
Output:
True
Explanation:
Your class construction must return a coroutine
object instead of its own instance.
回答3:
Another way to do this, for funsies:
class aobject(object): """Inheriting this class allows you to define an async __init__. So you can create objects by doing something like `await MyClass(params)` """ async def __new__(cls, *a, **kw): instance = super().__new__(cls) await instance.__init__(*a, **kw) return instance async def __init__(self): pass #With non async super classes class A: def __init__(self): self.a = 1 class B(A): def __init__(self): self.b = 2 super().__init__() class C(B, aobject): async def __init__(self): super().__init__() self.c=3 #With async super classes class D(aobject): async def __init__(self, a): self.a = a class E(D): async def __init__(self): self.b = 2 await super().__init__(1) # Overriding __new__ class F(aobject): async def __new__(cls): print(cls) return await super().__new__(cls) async def __init__(self): await asyncio.sleep(1) self.f = 6 loop = asyncio.get_event_loop() e = loop.run_until_complete(E()) e.b # 2 e.a # 1 c = loop.run_until_complete(C()) c.a # 1 c.b # 2 c.c # 3 f = loop.run_until_complete(F()) # Prints F class f.f # 6