Jak mogę zdefiniować klasę await
w konstruktorze lub treści klasy?
Na przykład to, czego chcę:
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'
lub przykład z atrybutem body 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)
Moje rozwiązanie (ale chciałbym zobaczyć bardziej elegancki sposób)
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
źródło
źródło
__new__
, chociaż może nie być eleganckie_pool_init(dsn)
a następnie wywołać ją z__init__
? Zachowałoby to wygląd init-in-constructor.@classmethod
😎 jest to alternatywny konstruktor. umieść tam pracę asynchroniczną; następnie w__init__
, po prostu ustawself
atrybutyOdpowiedzi:
Większość magicznych metod nie jest przeznaczona do pracy z
async def
/await
- generalnie powinieneś używać tylkoawait
wewnątrz dedykowanych asynchronicznych metod magicznych -__aiter__
,__anext__
,__aenter__
, i__aexit__
. Używanie go w innych magicznych metodach albo w ogóle nie zadziała, jak to ma miejsce w przypadku__init__
(chyba że użyjesz jakichś sztuczek opisanych w innych odpowiedziach tutaj), albo zmusi cię do używania wszystkiego, co wyzwala wywołanie metody magicznej w kontekście asynchronicznym.Istniejący
asyncio
biblioteki zwykle radzą sobie z tym na dwa sposoby: Po pierwsze, widziałem używany wzorzec fabryki (asyncio-redis
na przykład):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)
Inne biblioteki używają funkcji współrzędnych najwyższego poziomu, która tworzy obiekt, zamiast metody fabrycznej:
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)
create_pool
Funkcja zaiopg
który chcesz zadzwonić__init__
faktycznie używając dokładnie ten wzór.To przynajmniej rozwiązuje
__init__
problem. Nie widziałem zmiennych klas, które wykonują wywołania asynchroniczne w środowisku naturalnym, które mogę sobie przypomnieć, więc nie wiem, czy pojawiły się jakieś dobrze ugruntowane wzorce.źródło
Inny sposób na zrobienie tego, dla zabawy:
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())
źródło
__init__
semantyki, jeślisuper().__new__(cls)
zwraca wcześniej istniejące wystąpienie - normalnie byłoby to pomijane__init__
, ale twój kod nie.object.__new__
dokumentacją__init__
należy się powoływać tylko wtedy, gdyisinstance(instance, cls)
? Wydaje mi się to trochę niejasne ... Ale nigdzie nie widzę semantyki, którą twierdzisz ...__new__
aby zwrócić wcześniej istniejący obiekt, ten nowy musiałby być najbardziej zewnętrznym, aby miał jakikolwiek sens, ponieważ inne implementacje__new__
nie miałyby ogólnego sposobu na sprawdzenie, czy zwracasz nową niezainicjowaną instancję lub nie.async def __init__(...)
, jak pokazał OP, i uważam, żeTypeError: __init__() should return None, not 'coroutine'
wyjątek jest zakodowany na stałe w Pythonie i nie można go ominąć. Więc starałem się zrozumieć, jak toasync def __new__(...)
się zmieniło. Teraz rozumiem, że używaszasync def __new__(...)
(ab) cechy „jeśli__new__()
nie zwróci instancji cls, to__init__()
nie zostanie wywołane”. Twój nowy__new__()
zwraca coroutine, a nie cls. Dlatego. Sprytny hack!Poleciłbym osobną metodę fabryczną. To bezpieczne i proste. Jeśli jednak nalegasz na
async
wersję programu__init__()
, oto przykład: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
Stosowanie:
@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())
Wynik:
<class '__main__.Foo'> True
Wyjaśnienie:
Twoja konstrukcja klasy musi zwracać
coroutine
obiekt zamiast własnej instancji.źródło
new
__new__
i użyćsuper
(podobnie__init__
, tj. Po prostu pozwolić klientowi to zastąpić)?Jeszcze lepiej możesz zrobić coś takiego, co jest bardzo łatwe:
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))
Zasadniczo to, co się tutaj dzieje, jest
__init__()
jak zwykle wywoływane jako pierwsze. Następnie__await__()
zostaje wezwany, który następnie czekaasync_init()
.źródło
[Prawie] kanoniczna odpowiedź @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
źródło
dataclasses
dla zwycięstwa! tak łatwo.