python - metodo - Cómo establecer el atributo de clase con await en__init__
metodo str python (4)
¿Cómo puedo definir una clase con
await
en el constructor o cuerpo de 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 atributo de cuerpo de 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()
La mayoría de los métodos mágicos no están diseñados para funcionar con
async def
/
async def
; en general, solo debe usar
await
dentro de los métodos mágicos asincrónicos dedicados:
__aiter__
,
__anext__
,
__aenter__
y
__aexit__
.
Usarlo dentro de otros métodos mágicos no funcionará en absoluto (como es el caso con
__init__
), o lo obligará a usar siempre cualquier desencadenante que llame al método mágico en un contexto asincrónico.
Las bibliotecas
asyncio
existentes tienden a tratar 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 usan una función de rutina 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 función
create_pool
de
aiopg
que desea llamar
__init__
está usando este patrón exacto.
Esto al menos soluciona el problema
__init__
.
No he visto variables de clase que hagan llamadas asíncronas en la naturaleza que pueda recordar, así que no sé si han surgido patrones bien establecidos.
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
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
Recomendaría un método de fábrica separado.
Es seguro y sencillo.
Sin embargo, si insiste en una versión
async
de
__init__()
, aquí hay 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 objeto de
coroutine
lugar de su propia instancia.
Si está utilizando Python3.7 o posterior, puede usar asyncio.run :
import asyncio
# some code
class Foo(object):
async def __init(self):
self.pool = await create_pool(dsn)
def __init__(self, settings):
self.settings = settings
asyncio.run(self.__init)
foo = Foo(settings)
Tenga en cuenta que esto no funcionará si está creando instancias de
Foo
en una función asincrónica que ya se está ejecutando.
Vea
esta publicación de blog
para una discusión sobre cómo manejar este escenario y una buena discusión sobre programación asincrónica en Python.