Wie kann ich eine Klasse mit await
im Konstruktor oder im Klassenkörper definieren?
Zum Beispiel was ich will:
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'
oder Beispiel mit Klassenkörperattribut:
class Foo(object):
self.pool = await create_pool(dsn) # Sure it raises syntax Error
def __init__(self, settings):
self.settings = settings
foo = Foo(settings)
Meine Lösung (aber ich würde gerne einen eleganteren Weg sehen)
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
quelle
quelle
__new__
, obwohl es vielleicht nicht elegant ist_pool_init(dsn)
und sie dann aufzurufen__init__
? Es würde das Erscheinungsbild des Init-in-Konstruktors beibehalten.@classmethod
😎 es ist ein alternativer Konstruktor. lege die asynchrone Arbeit dort hin; Dann__init__
setzen Sie einfach dieself
AttributeAntworten:
Die meisten magischen Methoden sind nicht zur Arbeit entwickelt , um mit
async def
/await
- in der Regel sollten Sie nur verwenden werdenawait
innerhalb der dedizierten asynchronen magischen Methoden -__aiter__
,__anext__
,__aenter__
, und__aexit__
. Die Verwendung in anderen magischen Methoden funktioniert entweder überhaupt nicht, wie dies der Fall ist__init__
(es sei denn, Sie verwenden einige Tricks, die in anderen Antworten hier beschrieben sind), oder zwingt Sie, immer das zu verwenden, was den Aufruf der magischen Methode in einem asynchronen Kontext auslöst.Bestehende
asyncio
Bibliotheken können dies auf zwei Arten behandeln: Erstens habe ich das verwendete Factory-Muster gesehen (asyncio-redis
zum Beispiel):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)
Andere Bibliotheken verwenden eine Coroutine-Funktion der obersten Ebene, die das Objekt erstellt, und keine Factory-Methode:
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)
Die
create_pool
Funktion, vonaiopg
der aus Sie aufrufen möchten,__init__
verwendet genau dieses Muster.Dies behebt zumindest das
__init__
Problem. Ich habe keine Klassenvariablen gesehen, die asynchrone Aufrufe in freier Wildbahn ausführen, an die ich mich erinnern kann, daher weiß ich nicht, dass etablierte Muster entstanden sind.quelle
Ein anderer Weg, dies für Funsies zu tun:
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())
quelle
__init__
Semantik, wennsuper().__new__(cls)
eine bereits vorhandene Instanz zurückgegeben wird. Normalerweise wird dies übersprungen__init__
, Ihr Code jedoch nicht.object.__new__
Dokumentation__init__
sollte nur aufgerufen werden, wennisinstance(instance, cls)
? Dies scheint mir etwas unklar ... Aber ich sehe die Semantik, die Sie behaupten, nirgendwo ...__new__
, um ein bereits vorhandenes Objekt zurückzugeben, muss dieses neue das äußerste sein, um einen Sinn zu ergeben, da andere Implementierungen von__new__
keine allgemeine Möglichkeit haben, zu wissen, ob Sie eine neue nicht initialisierte Instanz zurückgeben oder nicht.async def __init__(...)
, wie das OP zeigt, und ich glaube, dass dieTypeError: __init__() should return None, not 'coroutine'
Ausnahme in Python fest codiert ist und nicht umgangen werden konnte. Also versuchte ich zu verstehen, wieasync def __new__(...)
es einen Unterschied machte. Mein Verständnis ist nun, dass Ihrasync def __new__(...)
(ab) das Merkmal "Wenn__new__()
keine Instanz von cls zurückgegeben wird,__init__()
wird es nicht aufgerufen" verwendet. Ihr neuer__new__()
gibt eine Coroutine zurück, keine cls. Deshalb. Cleverer Hack!Ich würde eine separate Factory-Methode empfehlen. Es ist sicher und unkompliziert. Wenn Sie jedoch auf einer
async
Version von bestehen__init__()
, finden Sie hier ein Beispiel: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
Verwendung:
@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())
Ausgabe:
<class '__main__.Foo'> True
Erläuterung:
Ihre Klassenkonstruktion muss ein
coroutine
Objekt anstelle einer eigenen Instanz zurückgeben.quelle
new
__new__
und verwendensuper
(ebenfalls für__init__
, dh lassen Sie den Client dies einfach überschreiben)?Besser noch, Sie können so etwas tun, was sehr einfach ist:
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))
Grundsätzlich wird hier
__init__()
wie gewohnt zuerst aufgerufen. Dann__await__()
wird gerufen, was dann wartetasync_init()
.quelle
[Fast] kanonische Antwort von @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
quelle
dataclasses
für den Sieg! so einfach.