如何在__init__中设置带有等待的类属性


92

如何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 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
您可能会有些运气__new__,尽管可能并不那么优雅
JBernardo

我没有使用3.5的经验,在其他语言中,由于async / await的病毒性质,因此无法正常工作,但是您是否尝试过定义类似async的函数_pool_init(dsn),然后从中调用它__init__?它将保留init-in-constructor的外观。
美联社


1
使用@classmethod😎这是一个替代构造函数。将异步工作放在那里;然后在中__init__,只需设置self属性
grisaitis

Answers:


117

最神奇的方法不旨在与工作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__问题。我还没有看到可以在野外进行异步调用的类变量,所以我不知道会出现任何完善的模式。


35

有趣的另一种方式:

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())

2
我认为,这是目前最清晰,最易理解的实施方式。我真的很喜欢它的直观扩展性。我担心有必要深入研究元类。
Tankobot

1
__init__如果super().__new__(cls)返回一个预先存在的实例,这将没有正确的语义-通常,这将跳过__init__,但是您的代码则不会。
埃里克(Eric)

嗯,根据object.__new__文档,__init__仅在以下情况下才应调用isinstance(instance, cls)?这对我来说似乎有点不清楚...但是我看不到您在任何地方声称的语义...
khazhyk

请多考虑一下,如果您重写__new__以返回先前存在的对象,则该new必须是最外面的才有意义,因为的其他实现__new__将无法通用地知道您是否要返回新的未初始化实例或不。
khazhyk

1
@khazhyk好吧,肯定有某些事情阻止您定义 async def __init__(...)正如OP所显示的,,并且我认为TypeError: __init__() should return None, not 'coroutine'异常是在Python内部进行硬编码的,因此无法绕过。因此,我试图了解这有何async def __new__(...)不同。现在我的理解是,您async def __new__(...)(ab)使用“如果__new__()不返回cls的实例,__init__()则将不会被调用”的特性。您的新产品__new__()返回协程,而不是cls。这就是为什么。聪明的骇客!
RayLuo

20

我建议使用单独的工厂方法。安全,简单。但是,如果您坚持使用的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__,即让客户端覆盖它)吗?
Matthias Urlichs,

7

更好的是,您可以执行以下操作,这很容易:

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()


3

[几乎] @ojii的规范答案

@dataclass
class Foo:
    settings: Settings
    pool: Pool

    @classmethod
    async def create(cls, settings: Settings, dsn):
        return cls(settings, await create_pool(dsn))

3
dataclasses为了胜利!太简单。
grisaitis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.