簡體   English   中英

如何在 __init__ 中使用 await 設置類屬性

[英]How to set class attribute with await in __init__

如何在構造函數或類主體中使用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()

大多數魔術方法並非設計為與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)

您要在__init__中調用的來自aiopgcreate_pool函數實際上正在使用這種精確模式。

這至少解決了__init__問題。 我還沒有看到我記得的在野外進行異步調用的類變量,所以我不知道是否出現了任何成熟的模式。

另一種方法來做到這一點,對於有趣:

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

我會推薦一個單獨的工廠方法。 這是安全和直接的。 但是,如果您堅持使用__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對象而不是它自己的實例。

更好的是你可以做這樣的事情,這很容易:

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

[幾乎] @ojii 的規范回答

@dataclass
class Foo:
    settings: Settings
    pool: Pool

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

如果您使用的是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中異步編程的精彩討論。

帶有__ainit__ "async-constructor" 的 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]"

這是“異步類”的示例:

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

用法:

async def example():
    my_obj = await MyAsyncObject("test", 123)

我想展示一種在__init__方法中啟動基於協程的方法的更簡單的方法。

import asyncio


class Foo(object):

    def __init__(self, settings):
        self.settings = settings
        loop = asyncio.get_event_loop() 
        self.pool = loop.run_until_complete(create_pool(dsn))

foo = Foo(settings)

需要注意的重要一點是:

  • 這使得異步代碼作為同步(阻塞)工作
  • 這不是運行異步代碼的最佳方式,但是當它僅通過同步方法啟動時,例如: __init__它將是一個很好的選擇。
  • 啟動后,您可以使用 await 從對象運行異步方法。 await foo.pool.get(value)
  • 不要嘗試通過await調用啟動,你會得到RuntimeError: This event loop is already running

到目前為止,Vishnu shettigar 的答案是最簡單的,除了他的async_init方法不返回對象本身,因此沒有為foo分配Foo實例。 至於OP的目的,構造類恕我直言的最優雅的方式是

import asyncio

class Foo:
    def __init__(self, settings):
        self.settings = settings

    def __await__(self):
        self.pool = asyncio.create_task(create_pool(dsn))
        yield from self.pool
        self.pool = self.pool.result()
        return self

要初始化對象,請執行以下操作

def main():
    loop = asyncio.get_event_loop()
    foo = loop.run_until_complete(Foo(settings))

或者

async def main():
    foo = await Foo(settings)

我們可以通過asyncio.run()手動運行異步代碼,將異步調用轉換為同步調用

class Foo:
    async def __ainit__(self, param):
        self._member = await some_async_func(param)

    def __init__(self, param):
        asyncio.run(self.__ainit__(param))

根據您的需要,您還可以使用來自以下網址的AwaitLoaderhttps ://pypi.org/project/async-property/

從文檔:

AwaitLoader將在加載屬性之前調用 await instance.load() (如果存在)。

這在 Python 3.9 中對我有用


from aiobotocore.session import AioSession
import asyncio




class SomeClass():

    def __init__(self):
        asyncio.run(self.async_init())
        print(self.s3)

    async def async_init(self):
        self.s3 = await AioSession().create_client('s3').__aenter__()

大家可以試試: https://pypi.org/project/asyncinit/

  • 點安裝異步初始化
from asyncinit import asyncinit

@asyncinit
class MyClass:
    async def __init__(self, param):
        self.val = await self.deferredFn(param)

    async def deferredFn(self, x):
        # ...
        return x + 2

obj = await MyClass(42)
assert obj.val == 44

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM