Как я могу определить класс 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'
или пример с атрибутом тела класса:
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()
python
python-3.x
python-asyncio
уралбаш
источник
источник
__new__
, хотя это может быть не изящно_pool_init(dsn)
а затем вызвать ее__init__
? Это сохранит внешний вид init-in-constructor.@classmethod
😎 это альтернативный конструктор. поместите туда асинхронную работу; затем__init__
просто установитеself
атрибутыОтветы:
Большинство магических методы не предназначены для работы с
async def
/await
- в общем, вы должны использовать толькоawait
внутри выделенные асинхронные методы магии -__aiter__
,__anext__
,__aenter__
, и__aexit__
. Использование его внутри других магических методов либо вообще не сработает, как в случае с__init__
(если вы не используете некоторые трюки, описанные в других ответах здесь), либо заставит вас всегда использовать все, что запускает вызов магического метода в асинхронном контексте.Существующие
asyncio
библиотеки, как правило, справляются с этим одним из двух способов: во-первых, я видел, как используется фабричный шаблон (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)
Другие библиотеки используют функцию сопрограммы верхнего уровня, которая создает объект, а не фабричный метод:
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__
проблему. Я не видел переменных класса, которые делают асинхронные вызовы в естественных условиях, которые я могу припомнить, поэтому я не знаю, возникли ли какие-либо устойчивые шаблоны.источник
Другой способ сделать это для фанатов:
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())
источник
__init__
семантики, еслиsuper().__new__(cls)
возвращает уже существующий экземпляр - обычно это пропускается__init__
, но ваш код нет.object.__new__
документации,__init__
следует вызывать только еслиisinstance(instance, cls)
? Мне это кажется несколько непонятным ... Но я нигде не вижу семантики, о которой вы говорите ...__new__
чтобы вернуть уже существующий объект, этот новый должен быть самым внешним, чтобы иметь какой-либо смысл, поскольку другие реализации не__new__
будут иметь общего способа узнать, возвращаете ли вы новый неинициализированный экземпляр или не.async def __init__(...)
, как показано OP, и я считаю, чтоTypeError: __init__() should return None, not 'coroutine'
исключение жестко запрограммировано внутри Python и не может быть обойдено. Поэтому я попытался понять, как этоasync def __new__(...)
изменилось. Теперь я понимаю, что вашasync def __new__(...)
(ab) использует характеристику «если__new__()
не возвращает экземпляр cls, то__init__()
не будет вызван». Ваш новый__new__()
возвращает сопрограмму, а не cls. Поэтому. Умный хак!Я бы порекомендовал отдельный заводской метод. Это безопасно и просто. Однако, если вы настаиваете на
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
объект вместо своего собственного экземпляра.источник
new
__new__
и использоватьsuper
(аналогично__init__
, т.е. просто позволить клиенту переопределить это) вместо этого?А еще лучше вы можете сделать что-то вроде этого, что очень просто:
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))
В основном то, что здесь происходит, вызывается
__init__()
первым, как обычно. Затем__await__()
вызывается, который затем ждетasync_init()
.источник
[Почти] канонический ответ @ojii
@dataclass class Foo: settings: Settings pool: Pool @classmethod async def create(cls, settings: Settings, dsn): return cls(settings, await create_pool(dsn))
источник
dataclasses
для победы! так просто.