如何在__init__中使用await设置class属性

ura*_*ash 62 python python-3.x python-asyncio

如何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'
Run Code Online (Sandbox Code Playgroud)

或者类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)
Run Code Online (Sandbox Code Playgroud)

我的解决方案(但我希望看到更优雅的方式)

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()
Run Code Online (Sandbox Code Playgroud)

dan*_*ano 81

大多数魔术方法设计没有工作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)
Run Code Online (Sandbox Code Playgroud)

其他库使用创建对象的顶级协同程序函数,而不是工厂方法:

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)
Run Code Online (Sandbox Code Playgroud)

您要调用的create_pool函数实际上是使用此精确模式.aiopg__init__

这至少解决了这个__init__问题.我还没有看到可以在野外进行异步调用的类变量,所以我不知道已经出现了任何完善的模式.


kha*_*hyk 23

换句话的另一种方法是:

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
Run Code Online (Sandbox Code Playgroud)

  • @khazhyk嗯,肯定有一些东西阻止你定义 `async def __init__(...)`,正如OP所示,我相信 `TypeError: __init__() 应该返回 None,而不是 'coroutine'` 异常是硬编码在Python内部并且无法被绕过。所以我试图理解“async def __new__(...)”是如何产生影响的。现在我的理解是,你的`async def __new__(...)` (ab)使用了“如果`__new__()`不返回cls实例,那么`__init__()`将不会被调用”的特性。您的新 __new__()` 返回一个协程,而不是一个 cls。这就是为什么。聪明的黑客! (3认同)
  • 我认为,这是目前最清晰,最易理解的实施方式。我真的很喜欢它的直观扩展性。我担心有必要深入研究元类。 (2认同)
  • 如果 `super().__new__(cls)` 返回一个预先存在的实例,则这不具有正确的 `__init__` 语义 - 通常,这会跳过 `__init__`,但您的代码不会。 (2认同)

Hua*_*Gao 18

我会推荐一个单独的工厂方法.这是安全和直接的.但是,如果你坚持使用的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
Run Code Online (Sandbox Code Playgroud)

用法:

@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
Run Code Online (Sandbox Code Playgroud)

async def f():
    print((await Foo()).initialized)

loop = asyncio.get_event_loop()
loop.run_until_complete(f())
Run Code Online (Sandbox Code Playgroud)

输出:

<class '__main__.Foo'>
True
Run Code Online (Sandbox Code Playgroud)

说明:

您的类构造必须返回一个coroutine对象而不是它自己的实例.

  • 嗯,有趣的伎俩.不知道我想用它. (12认同)

小智 15

更好的是你可以做这样的事情,这很容易:

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))
Run Code Online (Sandbox Code Playgroud)

基本上这里发生的事情__init__() 像往常一样首先被调用。然后__await__()被调用,然后等待async_init()

  • 我喜欢这种简单直接的初始化异步类的设计,但我认为你忘记在 `async_init()` 方法中编写 `return self` (3认同)

Art*_*nov 11

__ainit__具有“异步构造函数”的AsyncObj 类:

class AsyncObj:
    def __init__(self, *args, **kwargs):
        """
        Standard constructor used for arguments pass
        Do not override. Use __ainit__ instead
        """
        self.__storedargs = args, kwargs
        self.async_initialized = False

    async def __ainit__(self, *args, **kwargs):
        """ Async constructor, you should implement this """

    async def __initobj(self):
        """ Crutch used for __await__ after spawning """
        assert not self.async_initialized
        self.async_initialized = True
        await self.__ainit__(*self.__storedargs[0], **self.__storedargs[1])  # pass the parameters to __ainit__ that passed to __init__
        return self

    def __await__(self):
        return self.__initobj().__await__()

    def __init_subclass__(cls, **kwargs):
        assert asyncio.iscoroutinefunction(cls.__ainit__)  # __ainit__ must be async

    @property
    def async_state(self):
        if not self.async_initialized:
            return "[initialization pending]"
        return "[initialization done and successful]"
Run Code Online (Sandbox Code Playgroud)

这是“异步类”的示例:

class MyAsyncObject(AsyncObj):
    async def __ainit__(self, param1, param2=0):
        print("hello!", param1, param2)
        # go something async, e.g. go to db
    
Run Code Online (Sandbox Code Playgroud)

用法:

async def example():
    my_obj = await MyAsyncObject("test", 123)
Run Code Online (Sandbox Code Playgroud)


Dim*_*nek 8

[几乎] @ojii 的规范答案

@dataclass
class Foo:
    settings: Settings
    pool: Pool

    @classmethod
    async def create(cls, settings: Settings, dsn):
        return cls(settings, await create_pool(dsn))
Run Code Online (Sandbox Code Playgroud)

  • “数据类”获胜!太简单。 (4认同)

ala*_*lan 6

如果您使用的是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)
Run Code Online (Sandbox Code Playgroud)

请注意,如果要Foo在已经运行的异步函数中实例化,则此方法将无效。请参阅此博客文章,以获取有关如何处理这种情况的讨论,以及有关Python中异步编程的精彩讨论。