Comment définir une classe avec await
dans le constructeur ou le corps de la classe?
Par exemple ce que je veux:
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 exemple avec l'attribut class body:
class Foo(object):
self.pool = await create_pool(dsn) # Sure it raises syntax Error
def __init__(self, settings):
self.settings = settings
foo = Foo(settings)
Ma solution (mais j'aimerais voir une manière plus élégante)
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 plupart des méthodes magiques ne sont pas conçues pour fonctionner avec async def
/await
- en général, vous ne devriez utiliser que await
à l'intérieur des méthodes magiques asynchrones dédiées - __aiter__
, __anext__
, __aenter__
Et __aexit__
. Son utilisation dans d'autres méthodes magiques ne fonctionnera pas du tout (comme c'est le cas avec __init__
) Ou vous obligera à toujours utiliser ce qui déclenche l'appel de la méthode magique dans un contexte asynchrone.
Les bibliothèques asyncio
existantes ont tendance à traiter cela de deux manières: Tout d'abord, j'ai vu le modèle d'usine utilisé ( asyncio-redis
, par exemple):
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)
D'autres bibliothèques utilisent une fonction de coroutine de niveau supérieur qui crée l'objet, plutôt qu'une méthode d'usine:
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 fonction create_pool
De aiopg
que vous souhaitez appeler dans __init__
Utilise ce modèle exact.
Cela répond au moins au problème __init__
. Je n'ai pas vu de variables de classe qui effectuent des appels asynchrones dans la nature que je puisse me rappeler, alors je ne sais pas si des modèles bien établis ont émergé.
Une autre façon de faire, pour les 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
Je recommanderais une méthode d'usine séparée. C'est sûr et simple. Cependant, si vous insistez pour une version async
de __init__()
, voici un exemple:
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
tilisation:
@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())
Sortie:
<class '__main__.Foo'>
True
Explication:
Votre construction de classe doit renvoyer un objet coroutine
à la place de sa propre instance.
Si vous utilisez Python3.7 ou plus récent, vous pouvez utiliser 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)
Notez que cela ne fonctionnera pas si vous instanciez Foo
dans une fonction asynchrone en cours d'exécution. Voir this blog post pour une discussion sur la façon de gérer ce scénario et une discussion intéressante sur la programmation asynchrone en Python.