web-dev-qa-db-fra.com

Comment définir un attribut de classe avec wait dans __init__

Comment définir une classe avec await dans le constructeur ou le corps de la classe?

Par exemple ce que je veux:

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'

ou exemple avec l'attribut 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)

Ma solution (mais j'aimerais voir une manière plus élégante)

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

La plupart des méthodes magiques ne sont pas conçues pour fonctionner avec async def/await - en général, vous ne devriez utiliser que await à l'intérieur des méthodes magiques asynchrones dédiées - __aiter__, __anext__, __aenter__ Et __aexit__. Son utilisation dans d'autres méthodes magiques ne fonctionnera pas du tout (comme c'est le cas avec __init__) Ou vous obligera à toujours utiliser ce qui déclenche l'appel de la méthode magique dans un contexte asynchrone.

Les bibliothèques asyncio existantes ont tendance à traiter cela de deux manières: Tout d'abord, j'ai vu le modèle d'usine utilisé ( asyncio-redis , par exemple):

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)

D'autres bibliothèques utilisent une fonction de coroutine de niveau supérieur qui crée l'objet, plutôt qu'une méthode d'usine:

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)

La fonction create_pool De aiopg que vous souhaitez appeler dans __init__ Utilise ce modèle exact.

Cela répond au moins au problème __init__. Je n'ai pas vu de variables de classe qui effectuent des appels asynchrones dans la nature que je puisse me rappeler, alors je ne sais pas si des modèles bien établis ont émergé.

76
dano

Une autre façon de faire, pour les 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

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

Je recommanderais une méthode d'usine séparée. C'est sûr et simple. Cependant, si vous insistez pour une version async de __init__(), voici un exemple:

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

tilisation:

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

Sortie:

<class '__main__.Foo'>
True

Explication:

Votre construction de classe doit renvoyer un objet coroutine à la place de sa propre instance.

17
Huazuo Gao

Si vous utilisez Python3.7 ou plus récent, vous pouvez utiliser 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)

Notez que cela ne fonctionnera pas si vous instanciez Foo dans une fonction asynchrone en cours d'exécution. Voir this blog post pour une discussion sur la façon de gérer ce scénario et une discussion intéressante sur la programmation asynchrone en Python.

5
alan