Comment puis-je 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 de corps 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)
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()
python
python-3.x
python-asyncio
uralbash
la source
la source
__new__
, même si ce n'est peut-être pas élégant_pool_init(dsn)
et de l'appeler ensuite__init__
? Cela préserverait l'apparence d'init-in-constructeur.@classmethod
😎 c'est un constructeur alternatif. y mettre le travail asynchrone; puis dans__init__
, il suffit de définir lesself
attributsRéponses:
La plupart des méthodes magiques ne sont pas conçus pour fonctionner avec
async def
/await
- en général, vous ne devriez utiliser à l'await
intérieur des méthodes magiques asynchrones dédiés -__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__
(à moins que vous n'utilisiez certaines astuces décrites dans d'autres réponses ici), ou vous obligera à toujours utiliser tout ce qui déclenche l'appel de la méthode magique dans un contexte asynchrone.Les
asyncio
bibliothèques existantes ont tendance à gérer cela de deux manières: Premièrement, 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 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
create_pool
fonction à partir deaiopg
laquelle vous souhaitez appeler__init__
utilise en fait ce modèle exact.Cela résout au moins le
__init__
problème. Je n'ai pas vu de variables de classe qui font des appels asynchrones dans la nature dont je puisse me souvenir, donc je ne sais pas si des modèles bien établis ont émergé.la source
Une autre façon de faire cela, pour le plaisir:
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())
la source
__init__
sémantique correcte sisuper().__new__(cls)
renvoie une instance préexistante - normalement, cela sauterait__init__
, mais pas votre code.object.__new__
documentation,__init__
ne devrait être invoqué que siisinstance(instance, cls)
? Cela me semble quelque peu flou ... Mais je ne vois la sémantique que vous revendiquez nulle part ...__new__
pour retourner un objet préexistant, ce nouveau devrait être le plus extérieur pour avoir un sens, car d'autres implémentations de__new__
n'auraient aucun moyen général de savoir si vous retournez une nouvelle instance non initialisée ou ne pas.async def __init__(...)
, comme le montre l'OP, et je crois que cetteTypeError: __init__() should return None, not 'coroutine'
exception est codée en dur dans Python et ne peut pas être contournée. J'ai donc essayé de comprendre comment unasync def __new__(...)
faisait une différence. Maintenant, je crois comprendre que votreasync def __new__(...)
(ab) utilise la caractéristique de "si__new__()
ne renvoie pas une instance de cls, alors__init__()
ne sera pas invoqué". Votre nouveau__new__()
retourne une coroutine, pas un cls. C'est pourquoi. Astuce astucieuse!Je recommanderais une méthode d'usine distincte. C'est sûr et simple. Cependant, si vous insistez sur une
async
version 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
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())
Production:
<class '__main__.Foo'> True
Explication:
Votre construction de classe doit renvoyer un
coroutine
objet au lieu de sa propre instance.la source
new
__new__
et utilisersuper
(de même pour__init__
, c'est-à-dire laisser simplement le client remplacer cela) à la place?Mieux encore, vous pouvez faire quelque chose comme ça, ce qui est très simple:
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))
Fondamentalement, ce qui se passe ici est
__init__()
appelé en premier, comme d'habitude. Puis__await__()
est appelé qui attend ensuiteasync_init()
.la source
[Presque] réponse canonique de @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
la source
dataclasses
pour la victoire! si facile.