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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33128325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 12:49:23  来源:igfitidea点击:

How to set class attribute with await in __init__

pythonpython-3.xpython-asyncio

提问by uralbash

How can I define a class with awaitin the constructor or class body?

如何await在构造函数或类主体中定义类?

For example what I want:

例如我想要的:

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'

or example with class body attribute:

或具有 class 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)

My solution (But I would like to see a more elegant way)

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

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

采纳答案by dano

Most magic methods aren't designed to work with async def/await- in general, you should only be using awaitinside the dedicated asynchronous magic methods - __aiter__, __anext__, __aenter__, and __aexit__. Using it inside other magic methods either won't work at all (as is the case with __init__), or will force you to always use whatever triggers the magic method call in an asynchronous context.

最神奇的方法不旨在与工作async def/ await-在一般情况下,你应该只使用await专用异步魔术方法里面- ,__aiter____anext____aenter____aexit__。在其他魔术方法中使用它要么根本不起作用(就像 的情况__init__),要么会迫使您始终使用在异步上下文中触发魔术方法调用的任何内容。

Existing asynciolibraries tend to deal with this in one of two ways: First, I've seen the factory pattern used (asyncio-redis, for example):

现有的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)

Other libraries use a top-level coroutine function that creates the object, rather than a factory method:

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

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)

The create_poolfunction from aiopgthat you want to call in __init__is actually using this exact pattern.

您要调用的create_pool函数实际上是使用这种精确模式。aiopg__init__

This at least addresses the __init__issue. I haven't seen class variables that make asynchronous calls in the wild that I can recall, so I don't know that any well-established patterns have emerged.

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

回答by Huazuo Gao

I would recommend a separate factory method. It's safe and straightforward. However, if you insist on a asyncversion of __init__(), here's an example:

我会推荐一个单独的工厂方法。这是安全和直接的。但是,如果您坚持使用 的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

Usage:

用法:

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

Output:

输出:

<class '__main__.Foo'>
True

Explanation:

解释:

Your class construction must return a coroutineobject instead of its own instance.

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

回答by khazhyk

Another way to do this, for funsies:

另一种方法来做到这一点,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

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

回答by Vishnu shettigar

Better yet you can do something like this, which is very easy:

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

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

Basically what happens here is __init__()gets called first as usual. Then __await__()gets called which then awaits async_init().

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

回答by Dima Tisnek

[Almost] canonical answer by @ojii

[几乎]@ojii 的规范答案

@dataclass
class Foo:
    settings: Settings
    pool: Pool

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