Como posso definir uma classe com await
no construtor ou corpo da classe?
Por exemplo, o que eu quero:
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'
ou exemplo com atributo de corpo de classe:
class Foo(object):
self.pool = await create_pool(dsn) # Sure it raises syntax Error
def __init__(self, settings):
self.settings = settings
foo = Foo(settings)
Minha solução (mas gostaria de ver uma forma mais elegante)
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()
python
python-3.x
python-asyncio
uralbash
fonte
fonte
__new__
, embora possa não ser elegante_pool_init(dsn)
e depois chamá-la de__init__
? Isso preservaria a aparência do init-no-construtor.@classmethod
😎 é um construtor alternativo. coloque o trabalho assíncrono lá; em seguida__init__
, basta definir osself
atributosRespostas:
A maioria dos métodos mágicos não são projetados para trabalhar com
async def
/await
- em geral, você só deve estar usandoawait
dentro dos métodos mágicos assíncronos dedicados -__aiter__
,__anext__
,__aenter__
, e__aexit__
. Usá-lo dentro de outros métodos mágicos não funcionará, como é o caso com__init__
(a menos que você use alguns truques descritos em outras respostas aqui), ou irá forçá-lo a sempre usar o que quer que acione a chamada do método mágico em um contexto assíncrono.asyncio
Bibliotecas existentes tendem a lidar com isso de duas maneiras: primeiro, eu vi o padrão de fábrica usado (asyncio-redis
por exemplo):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)
Outras bibliotecas usam uma função de co-rotina de nível superior que cria o objeto, em vez de um método de fábrica:
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)
A
create_pool
funçãoaiopg
que você deseja chamar__init__
está, na verdade, usando esse padrão exato.Isso pelo menos resolve o
__init__
problema. Eu não vi variáveis de classe que fazem chamadas assíncronas que eu possa me lembrar, então não sei se surgiram padrões bem estabelecidos.fonte
Outra maneira de fazer isso, por diversão:
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 async def main(): e = await E() print(e.b) # 2 print(e.a) # 1 c = await C() print(c.a) # 1 print(c.b) # 2 print(c.c) # 3 f = await F() # Prints F class print(f.f) # 6 import asyncio loop = asyncio.get_event_loop() loop.run_until_complete(main())
fonte
__init__
semântica correta sesuper().__new__(cls)
retorna uma instância pré-existente - normalmente, isso iria pular__init__
, mas seu código não.object.__new__
documentação,__init__
só deve ser invocado seisinstance(instance, cls)
? Isso parece um pouco confuso para mim ... Mas eu não vejo a semântica que você afirma em lugar nenhum ...__new__
para retornar um objeto pré-existente, esse novo precisaria ser o mais externo para fazer algum sentido, já que outras implementações de__new__
não teriam uma maneira geral de saber se você está retornando uma nova instância não inicializada ou não.async def __init__(...)
, como mostrado pelo OP, e eu acredito que essaTypeError: __init__() should return None, not 'coroutine'
exceção está codificada dentro do Python e não pode ser contornada. Tentei entender como issoasync def __new__(...)
fazia diferença. Agora, meu entendimento é que, seuasync def __new__(...)
(ab) use a característica de "se__new__()
não retornar uma instância de cls, então__init__()
não será invocado". Seu novo__new__()
retorna uma co-rotina, não um cls. É por isso. Hack inteligente!Eu recomendaria um método de fábrica separado. É seguro e simples. No entanto, se você insiste em uma
async
versão de__init__()
, aqui está um exemplo: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
Uso:
@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())
Resultado:
<class '__main__.Foo'> True
Explicação:
A construção de sua classe deve retornar um
coroutine
objeto em vez de sua própria instância.fonte
new
__new__
e usarsuper
(da mesma forma para__init__
, ou seja, apenas deixar o cliente substituir isso)?Melhor ainda, você pode fazer algo assim, o que é muito fácil:
import asyncio class Foo: def __init__(self, settings): self.settings = settings async def async_init(self): await create_pool(dsn) def __await__(self): return self.async_init().__await__() loop = asyncio.get_event_loop() foo = loop.run_until_complete(Foo(settings))
Basicamente, o que acontece aqui é
__init__()
chamado primeiro, como de costume. Em seguida,__await__()
é chamado e aguardaasync_init()
.fonte
Resposta [quase] canônica por @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
fonte
dataclasses
para a vitória! tão fácil.Dependendo de suas necessidades, você também pode usar
AwaitLoader
de: https://pypi.org/project/async-property/Dos documentos:
fonte