วิธีตั้งค่าแอตทริบิวต์คลาสด้วยการรอใน __init__


92

ฉันจะกำหนดคลาสด้วย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'

หรือตัวอย่างที่มีคลาส 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)

วิธีแก้ปัญหาของฉัน (แต่ฉันอยากเห็นวิธีที่หรูหรากว่านี้)

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__แม้ว่ามันอาจจะไม่สวยหรู
ก็ตาม

ฉันไม่มีประสบการณ์กับ 3.5 และในภาษาอื่นสิ่งนี้ใช้ไม่ได้เนื่องจากลักษณะไวรัสของ async / await แต่คุณได้ลองกำหนดฟังก์ชัน async _pool_init(dsn)แล้วเรียกใช้จาก__init__อะไร? มันจะรักษาลักษณะเริ่มต้นในตัวสร้าง
เม.ย.

1
หากคุณใช้ curio: curio.readthedocs.io/en/latest/…
matsjoyce

1
ใช้@classmethod😎เป็นตัวสร้างทางเลือก ใส่ async ทำงานที่นั่น จากนั้น__init__ตั้งค่าselfคุณลักษณะ
grisaitis

คำตอบ:


117

วิธีมายากลส่วนใหญ่จะไม่ได้ออกแบบมาเพื่อทำงานร่วมกับ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)

create_poolฟังก์ชั่นจากaiopgที่คุณต้องการที่จะเรียกใน__init__ที่เป็นจริงโดยใช้รูปแบบนี้แน่นอน

อย่างน้อยสิ่งนี้ก็ช่วย__init__แก้ปัญหาได้ ฉันไม่เห็นตัวแปรคลาสที่ทำให้การโทรแบบอะซิงโครนัสในไวลด์ที่ฉันจำได้ดังนั้นฉันจึงไม่รู้ว่ามีรูปแบบใด ๆ ที่เป็นที่ยอมรับแล้ว


36

อีกวิธีหนึ่งในการทำสิ่งนี้เพื่อความสนุกสนาน:

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
นี่เป็นการใช้งานที่ชัดเจนและเข้าใจได้มากที่สุดในความคิดของฉัน ฉันชอบมากที่มันขยายออกได้โดยสัญชาตญาณ ฉันกังวลว่าจะต้องเจาะลึกเมตาแว่นตา
Tankobot

1
สิ่งนี้ไม่มี__init__ความหมายที่ถูกต้องหากsuper().__new__(cls)ส่งคืนอินสแตนซ์ที่มีอยู่แล้ว - โดยปกติการดำเนินการนี้จะข้าม__init__ไป แต่รหัสของคุณไม่มี
Eric

อืมตามobject.__new__เอกสาร__init__ควรเรียกใช้ถ้าisinstance(instance, cls)? ดูเหมือนจะไม่ชัดเจนสำหรับฉัน ... แต่ฉันไม่เห็นความหมายที่คุณอ้างถึงที่ใดเลย ...
khazhyk

เมื่อคิดถึงสิ่งนี้มากขึ้นหากคุณลบล้าง__new__เพื่อส่งคืนวัตถุที่มีอยู่แล้วสิ่งใหม่นั้นจะต้องอยู่ด้านนอกสุดเพื่อให้สมเหตุสมผลเนื่องจากการใช้งานอื่น ๆ__new__จะไม่มีทางทราบโดยทั่วไปว่าคุณกำลังส่งคืนอินสแตนซ์ที่ไม่ได้เริ่มต้นใหม่หรือ ไม่.
khazhyk

1
@khazhyk มีบางอย่างที่ขัดขวางไม่ให้คุณกำหนดasync def __init__(...)ดังที่แสดงโดย OP และฉันเชื่อว่าTypeError: __init__() should return None, not 'coroutine'ข้อยกเว้นนั้นเข้ารหัสใน Python และไม่สามารถข้ามได้ ฉันจึงพยายามทำความเข้าใจว่ามันasync def __new__(...)สร้างความแตกต่างได้อย่างไร ตอนนี้ความเข้าใจของฉันคือasync def __new__(...)(ab) ของคุณใช้ลักษณะของ "ถ้า__new__()ไม่ส่งคืนตัวอย่างของ cls ก็__init__()จะไม่ถูกเรียกใช้" ใหม่ของคุณ__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

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.