¿Cómo puedo definir una clase await
en el constructor o en el cuerpo de la clase?
Por ejemplo lo que quiero:
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'
o ejemplo con el atributo del cuerpo de la clase:
class Foo(object):
self.pool = await create_pool(dsn) # Sure it raises syntax Error
def __init__(self, settings):
self.settings = settings
foo = Foo(settings)
Mi solución (pero me gustaría ver una forma más 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
fuente
fuente
__new__
, aunque puede que no sea elegante_pool_init(dsn)
y luego llamarla desde__init__
? Conservaría la apariencia de init-in-constructor.@classmethod
😎 es un constructor alternativo. ponga el trabajo asincrónico allí; luego adentro__init__
, simplemente establezca losself
atributosRespuestas:
La mayoría de los métodos mágicos no están diseñados para trabajar con
async def
/await
- en general, sólo se debe a utilizarawait
dentro de los métodos mágicos dedicados asíncronos -__aiter__
,__anext__
,__aenter__
, y__aexit__
. Usarlo dentro de otros métodos mágicos no funcionará en absoluto, como es el caso con__init__
(a menos que use algunos de los trucos descritos en otras respuestas aquí), o lo obligará a usar siempre lo que desencadena la llamada al método mágico en un contexto asincrónico.Las
asyncio
bibliotecas existentes tienden a lidiar con esto de una de dos maneras: Primero, he visto el patrón de fábrica utilizado (asyncio-redis
por ejemplo):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)
Otras bibliotecas utilizan una función de corrutina de nivel superior que crea el objeto, en lugar de un 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)
La
create_pool
función desde laaiopg
que desea llamar__init__
está usando este patrón exacto.Esto al menos aborda el
__init__
problema. No he visto variables de clase que hagan llamadas asincrónicas en la naturaleza que pueda recordar, por lo que no sé si han surgido patrones bien establecidos.fuente
Otra forma de hacer esto, por diversión:
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())
fuente
__init__
semántica correcta sisuper().__new__(cls)
devuelve una instancia preexistente; normalmente, esto saltaría__init__
, pero su código no.object.__new__
documentación,__init__
solo debe invocarse siisinstance(instance, cls)
? Esto parece algo claro para mí ... Pero no veo la semántica que reclama en cualquier lugar ...__new__
para devolver un objeto preexistente, ese nuevo debería ser el más externo para tener algún sentido, ya que otras implementaciones de__new__
no tendrían una forma general de saber si está devolviendo una nueva instancia no inicializada o no.async def __init__(...)
, como lo muestra el OP, y creo que laTypeError: __init__() should return None, not 'coroutine'
excepción está codificada dentro de Python y no se puede omitir. Así que traté de entender cómoasync def __new__(...)
marcó la diferencia. Ahora, según tengo entendido, suasync def __new__(...)
(ab) usa la característica de "si__new__()
no devuelve una instancia de cls, entonces__init__()
no se invocará". Su nuevo__new__()
devuelve una corrutina, no un cls. Es por eso. ¡Hack inteligente!Recomendaría un método de fábrica por separado. Es seguro y sencillo. Sin embargo, si insiste en una
async
versión de__init__()
, aquí tiene un ejemplo: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())
Salida:
<class '__main__.Foo'> True
Explicación:
La construcción de su clase debe devolver un
coroutine
objeto en lugar de su propia instancia.fuente
new
__new__
y usarsuper
(también para__init__
, es decir, dejar que el cliente lo anule) en su lugar?Mejor aún, puede hacer algo como esto, que es muy 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))
Básicamente, lo que sucede aquí es que
__init__()
se llama primero como de costumbre. Luego__await__()
se llama que luego esperaasync_init()
.fuente
[Casi] respuesta canónica de @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
fuente
dataclasses
¡por la victoria! tan fácil.