web-dev-qa-db-ja.com

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

コンストラクターまたはクラス本体で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()
58
uralbash

ほとんどのマジックメソッドは、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__の問題に対処します。私が思い出すことができる野生で非同期呼び出しを行うクラス変数を見たことがないので、確立されたパターンが出現したことを知りません。

76
dano

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

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
21
khazhyk

別のファクトリーメソッドをお勧めします。それは安全で簡単です。ただし、__init__()asyncバージョンを使用する場合は、次の例をご覧ください。

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オブジェクトを返す必要があります。

17
Huazuo Gao

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)

既に実行中の非同期関数でFooをインスタンス化している場合、これは機能しません。 このブログ投稿 を参照して、このシナリオの処理方法に関する議論と、Pythonでの非同期プログラミングの素晴らしい議論をしてください。

5
alan