Cách đặt thuộc tính lớp với await trong __init__


92

Làm cách nào tôi có thể xác định một lớp với awaittrong hàm tạo hoặc thân lớp?

Ví dụ những gì tôi muốn:

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'

hoặc ví dụ với thuộc tính 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)

Giải pháp của tôi (Nhưng tôi muốn xem một cách thanh lịch hơn)

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
Bạn có thể có một số may mắn với __new__, mặc dù nó có thể không được thanh lịch
JBernardo

Tôi không có kinh nghiệm với 3.5 và trong các ngôn ngữ khác, điều này sẽ không hoạt động vì tính chất lan truyền của async / await, nhưng bạn đã thử định nghĩa một hàm async như thế nào _pool_init(dsn)và sau đó gọi nó từ __init__đâu chưa? Nó sẽ duy trì sự xuất hiện của init-in-constructor.
ap

1
Nếu bạn sử dụng đồ cổ: curio.readthedocs.io/en/latest/...
matsjoyce

1
sử dụng @classmethod😎 nó là một hàm tạo thay thế. đặt công việc không đồng bộ ở đó; sau đó __init__, chỉ cần đặt các selfthuộc tính
grisaitis

Câu trả lời:


117

Hầu hết các phương pháp kỳ diệu không được thiết kế để làm việc với async def/ await- nói chung, bạn chỉ nên sử dụng awaitbên trong các phương pháp kỳ diệu không đồng bộ chuyên dụng - __aiter__, __anext__, __aenter__, và __aexit__. Sử dụng nó bên trong các phương pháp ma thuật khác sẽ không hoạt động chút nào, như trường hợp của nó __init__(trừ khi bạn sử dụng một số thủ thuật được mô tả trong các câu trả lời khác ở đây), hoặc sẽ buộc bạn phải luôn sử dụng bất kỳ thứ gì kích hoạt lệnh gọi phương thức ma thuật trong bối cảnh không đồng bộ.

Các asynciothư viện hiện tại có xu hướng xử lý vấn đề này theo một trong hai cách: Đầu tiên, tôi đã thấy mẫu gốc được sử dụng ( asyncio-redisví dụ:):

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)

Các thư viện khác sử dụng một hàm coroutine cấp cao nhất để tạo đối tượng, thay vì một phương thức gốc:

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)

Các create_poolchức năng từ aiopgmà bạn muốn gọi __init__được thực sự sử dụng mô hình này chính xác.

Điều này ít nhất giải quyết __init__vấn đề. Tôi chưa thấy các biến lớp thực hiện lệnh gọi không đồng bộ trong tự nhiên mà tôi có thể nhớ lại, vì vậy tôi không biết rằng bất kỳ mẫu được thiết lập tốt nào đã xuất hiện.


36

Một cách khác để làm điều này, dành cho những người yêu thích:

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
Đây là cách thực hiện rõ ràng và dễ hiểu nhất theo tôi. Tôi thực sự thích cách nó có thể mở rộng trực quan. Tôi đã lo lắng rằng sẽ cần thiết phải nghiên cứu sâu hơn về kính đo.
Tankobot

1
Điều này không có __init__ngữ nghĩa chính xác nếu super().__new__(cls)trả về một phiên bản đã có từ trước - thông thường, điều này sẽ bỏ qua __init__, nhưng mã của bạn thì không.
Eric

Hmm, theo object.__new__tài liệu, __init__chỉ nên được gọi nếu isinstance(instance, cls)? Điều này có vẻ hơi mơ hồ đối với tôi ... Nhưng tôi không thấy ngữ nghĩa bạn cho bất cứ nơi nào ...
khazhyk

Suy nghĩ về điều này nhiều hơn, nếu bạn ghi đè __new__để trả về một đối tượng đã có từ trước, thì đối tượng mới đó sẽ cần phải là đối tượng ngoài cùng để có ý nghĩa, vì các triển khai khác của __new__sẽ không có cách nào để biết liệu bạn đang trả lại một phiên bản chưa được khởi tạo mới hay không phải.
khazhyk

1
@khazhyk Chà, chắc chắn có điều gì đó ngăn cản bạn xác định async def __init__(...), như OP đã chỉ ra và tôi tin rằng TypeError: __init__() should return None, not 'coroutine'ngoại lệ đó được mã hóa cứng bên trong Python và không thể bỏ qua. Vì vậy, tôi đã cố gắng hiểu cách một async def __new__(...)tạo ra sự khác biệt. Bây giờ tôi hiểu rằng, async def __new__(...)(ab) của bạn sử dụng đặc tính "nếu __new__()không trả về một thể hiện của cls, thì __init__()sẽ không được gọi". Mới của bạn __new__()trả về một quy trình đăng quang, không phải một cls. Đó là lý do tại sao. Hack thông minh!
RayLuo

20

Tôi muốn giới thiệu một phương pháp nhà máy riêng biệt. Nó an toàn và đơn giản. Tuy nhiên, nếu bạn nhấn mạnh vào một asyncphiên bản của __init__(), đây là một ví dụ:

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

Sử dụng:

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

Đầu ra:

<class '__main__.Foo'>
True

Giải trình:

Cấu trúc lớp của bạn phải trả về một coroutineđối tượng thay vì thể hiện của chính nó.


Bạn không thể đặt tên của mình new __new__và sử dụng super(tương tự như vậy __init__, tức là chỉ để khách hàng ghi đè lên) thay thế?
Matthias Urlichs

7

Tốt hơn hết là bạn có thể làm điều gì đó như thế này, rất dễ dàng:

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

Về cơ bản những gì xảy ra ở đây __init__() được gọi là đầu tiên như bình thường. Sau đó, __await__()được gọi mà sau đó chờ đợi async_init().


3

[Hầu như] câu trả lời chuẩn của @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để giành chiến thắng! quá dễ.
grisaitis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.