__init__でawaitを使用してクラス属性を設定する方法


95

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

1
__new__エレガントではないかもしれませんが、運が良かったかもしれません
JBernardo 2015年

私は3.5の経験がなく、他の言語ではasync / awaitのウイルス性のためにこれは機能しませんが、のような非同期関数を定義し_pool_init(dsn)てからそれを呼び出してみました__init__か?init-in-constructorの外観を保持します。
ap


1
@classmethod😎を使用してください。これは代替コンストラクターです。非同期作業をそこに置きます。その後で__init__、ちょうどセットselfの属性を
grisaitis

回答:


120

ほとんどの魔法のメソッドはで動作するように設計されていないasync def/ await-一般的に、あなただけ使用する必要がありますawait-専用の非同期マジックメソッドの内部で__aiter____anext____aenter__、と__aexit__。他のマジックメソッド内でそれを使用すると、__init__(ここで他の回答で説明されているいくつかのトリックを使用しない限り)の場合のようにまったく機能しないか、非同期コンテキストでマジックメソッド呼び出しをトリガーするものを常に使用する必要があります。

既存のasyncioライブラリは、次の2つの方法のいずれかでこれを処理する傾向があります。最初に、使用されるファクトリパターンを確認しました(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__問題に対処します。私が思い出すことができる非同期呼び出しを実際に行うクラス変数を見たことがないので、確立されたパターンが出現したことを知りません。


38

ファンシーのために、これを行う別の方法:

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
これは現在、私の意見では最も明確で理解しやすい実装です。私はそれがどれほど直感的に拡張可能であるかが本当に好きです。メタクラスを掘り下げる必要があるのではないかと心配していました。
タンコボット2018

1
既存のインスタンスを返す__init__場合、これは正しいセマンティクスを持っていませんsuper().__new__(cls)-通常、これはスキップし__init__ますが、コードはスキップしません。
エリック

うーん、object.__new__ドキュメントごとに、次の__init__場合にのみ呼び出す必要がありますかisinstance(instance, cls)?これは私にはやや不明確に思えます...しかし、あなたが主張するセマンティクスはどこにも
見当たり

これについてさらに考えると、__new__既存のオブジェクトを返すようにオーバーライドする場合、他の実装に__new__は初期化されていない新しいインスタンスを返すかどうかを知る一般的な方法がないため、その新しいものが意味をなすために最も外側にある必要があります。そうではありません。
khazhyk

1
@khazhykええと、async def __init__(...)OPで示されているように、定義を妨げる何かが確かにあります。TypeError: __init__() should return None, not 'coroutine'例外はPython内にハードコードされており、バイパスできなかったと思います。それで、私はどのようにasync def __new__(...)違いが生じたかを理解しようとしました。私の理解ではasync def __new__(...)、「__new__()clsのインスタンスを返さない場合は__init__()呼び出されない」という特性を(ab)使用します。あなたの新しいもの__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 2018年

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.