__İnit__ içinde await ile sınıf özniteliği nasıl ayarlanır


92

awaitYapıcı veya sınıf gövdesinde bir sınıfı nasıl tanımlayabilirim ?

Örneğin istediğim şey:

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'

veya sınıf gövdesi niteliğine sahip örnek:

class Foo(object):

    self.pool = await create_pool(dsn)  # Sure it raises syntax Error

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

foo = Foo(settings)

Çözümüm (Ama daha zarif bir yol görmek isterim)

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__Zarif olmasa da biraz şansınız olabilir
JBernardo

3.5 ile deneyimim yok ve diğer dillerde bu, async / await'in viral doğası nedeniyle işe yaramaz, ama bir async işlevi gibi tanımlamayı _pool_init(dsn)ve sonra onu çağırmayı denediniz __init__mi? Init-in-constructor görünümünü koruyacaktır.
ap


1
kullanmak @classmethodbir alternatif yapıcı bulunuyor 😎. oraya zaman uyumsuz çalışma koyun; sonra __init__, sadece selföznitelikleri ayarlayın
grisaitis

Yanıtlar:


117

En sihirli yöntemler ile çalışmak için tasarlanmamıştır async def/ awaitgenel olarak, yalnızca kullanıyor olmalıdır - awaitadanmış asenkron sihirli yöntemleri içinde - __aiter__, __anext__, __aenter__, ve __aexit__. Onu diğer sihirli yöntemlerin içinde kullanmak ya hiç çalışmaz __init__(burada diğer cevaplarda açıklanan bazı hileleri kullanmazsanız) ya da sizi her zaman sihirli yöntem çağrısını eşzamansız bir bağlamda tetikleyen her şeyi kullanmaya zorlar.

Mevcut asynciokütüphaneler bununla iki yoldan biriyle ilgilenme eğilimindedir: İlk olarak, kullanılan fabrika modelini gördüm ( asyncio-redisörneğin):

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)

Diğer kitaplıklar, bir fabrika yöntemi yerine nesneyi oluşturan üst düzey bir coroutine işlevi kullanır:

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_poolDan işlev aiopgiçeri çağırmak istediğiniz __init__aslında bu tam olarak işe kullanıyor.

Bu en azından __init__sorunu çözer. Hatırlayabildiğim kadarıyla vahşi ortamda eşzamansız çağrılar yapan sınıf değişkenleri görmedim, bu yüzden herhangi bir köklü modelin ortaya çıktığını bilmiyorum.


35

Eğlenceler için bunu yapmanın başka bir yolu:

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
Bence şu anda en açık ve anlaşılır uygulama bu. Sezgisel olarak ne kadar genişletilebilir olduğunu gerçekten seviyorum. Metasınıflara dalmanın gerekeceğinden endişeliydim.
Tankobot

1
Önceden var olan bir örneği döndürürse bu doğru __init__anlam bilgisine sahip değildir super().__new__(cls)- normalde bu atlanır __init__, ancak kodunuz olmaz.
Eric

Hmm, object.__new__dokümantasyona göre, __init__yalnızca aşağıdaki durumlarda çağrılmalıdır isinstance(instance, cls)? Bu bana biraz belirsiz geliyor ... Ama hiçbir yerde iddia ettiğiniz anlambilim görmüyorum ...
khazhyk

Bunu daha fazla düşünürsek, __new__önceden var olan bir nesneyi geri döndürmek için geçersiz kılarsanız , mantıklı olması için yeninin en dışta olması gerekir, çünkü diğer uygulamalarının __new__yeni bir başlatılmamış örneği mi döndürdüğünüzü mü yoksa bilmenin genel bir yolu yoktur. değil.
khazhyk

1
@khazhyk async def __init__(...)OP'nin gösterdiği gibi, kesinlikle sizi tanımlamanıza engel olan bir şey var ve ben TypeError: __init__() should return None, not 'coroutine'istisnanın Python içinde kodlandığına ve atlanamayacağına inanıyorum . Bu yüzden async def __new__(...)bir farkın nasıl bir fark yarattığını anlamaya çalıştım . Şimdi anladığım kadarıyla, sizin async def __new__(...)(ab) "eğer __new__()bir cls örneğini döndürmezse, o __init__()zaman çağrılmayacaktır" özelliğini kullanır. Yeni __new__()cihazınız bir cls değil, bir coroutine döndürür. Bu yüzden. Akıllı hack!
RayLuo

20

Ayrı bir fabrika yöntemi tavsiye ederim. Güvenli ve anlaşılır. Ancak, bir asyncsürümünde ısrar ediyorsanız __init__(), işte bir örnek:

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

Kullanım:

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

Çıktı:

<class '__main__.Foo'>
True

Açıklama:

Sınıf yapınız coroutinekendi örneği yerine bir nesne döndürmelidir .


Onun yerine kendi adınızı new __new__ve kullanamazsınız super(aynı şekilde __init__, yani sadece müşterinin bunu geçersiz kılmasına izin verin)
Matthias Urlichs

7

Daha da iyisi, bunun gibi bir şey yapabilirsiniz, ki bu çok kolaydır:

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

Temelde burada olan __init__() şey her zamanki gibi önce çağrılır. Sonra __await__()aranır ve daha sonra beklenir async_init().


3

@Ojii tarafından [Neredeyse] kanonik yanıt

@dataclass
class Foo:
    settings: Settings
    pool: Pool

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

3
dataclasseskazanmak için! çok kolay.
grisaitis
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.