Как установить атрибут класса с ожиданием в __init__
Как я могу определить класс с await
в конструкторе или в классе?
Например, что я хочу:
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'
или пример с атрибутом body class:
class Foo(object):
self.pool = await create_pool(dsn) # Sure it raises syntax Error
def __init__(self, settings):
self.settings = settings
foo = Foo(settings)
Мое решение (Но я хотел бы увидеть более элегантный способ)
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()
Ответы
Ответ 1
Большинство магических методов не предназначены для работы с async def
/await
- в общем, вы должны использовать только await
внутри выделенных асинхронных магических методов - __aiter__
, __anext__
, __aenter__
, и __aexit__
. Использование его в других магических методах либо вообще не будет работать (как в случае с __init__
), либо заставит вас всегда использовать любые триггеры, вызываемые магическим методом в асинхронном контексте.
Существующие библиотеки asyncio
имеют дело с этим одним из двух способов: во-первых, я использовал шаблон factory (asyncio-redis
, например):
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)
В других библиотеках используется функция сопрограммы верхнего уровня, которая создает объект, а не метод factory:
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
из aiopg
, которую вы хотите вызвать в __init__
, фактически использует этот точный шаблон.
Это, по крайней мере, устраняет проблему __init__
. Я не видел переменные класса, которые делают асинхронные вызовы в дикой природе, которые я могу вспомнить, поэтому я не знаю, что все хорошо устоявшиеся шаблоны появились.
Ответ 2
Я бы порекомендовал отдельный метод factory. Это безопасно и просто. Однако, если вы настаиваете на async
версии __init__()
, вот пример:
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
Применение:
@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())
Вывод:
<class '__main__.Foo'>
True
Объяснение:
Ваше построение класса должно возвращать объект coroutine
вместо своего собственного экземпляра.
Ответ 3
Другой способ сделать это, для 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
Ответ 4
Если вы используете Python3.7 или новее, вы можете использовать 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)
Обратите внимание, что это не сработает, если вы создаете Foo
в уже запущенной асинхронной функции. См. Это сообщение в блоге для обсуждения того, как справиться с этим сценарием, и приятного обсуждения асинхронного программирования в Python.