__init__에서 await로 클래스 속성을 설정하는 방법


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'

또는 클래스 본문 속성이있는 예 :

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__는 우아하지 않을 수도 있지만,
JBernardo

3.5에 대한 경험이 없으며 다른 언어에서는 async / await의 바이러스 성 특성 때문에 작동하지 않지만 _pool_init(dsn)다음 과 같은 비동기 함수를 정의한 다음 호출 해 보셨습니까 __init__? init-in-constructor 모양을 유지합니다.
ap


1
사용 @classmethod이 다른 생성자의 😎. 거기에 비동기 작업을 넣으십시오. 그런 다음에서 속성을 __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__문제를 해결합니다. 내가 기억할 수있는 비동기 호출을 수행하는 클래스 변수를 본 적이 없어서 잘 확립 된 패턴이 나타 났는지 모르겠습니다.


35

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

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__만 코드는 그렇지 않습니다.
에릭

흠, object.__new__문서에 따라 다음과 같은 __init__경우에만 호출해야합니다 isinstance(instance, cls). ...이 나에게 다소 불분명 보인다 ...하지만 난 당신이 어디 주장하는 의미를 볼 수 없습니다
khazhyk

이것에 대해 더 생각해 보면, __new__기존 객체를 반환하도록 재정의 하는 경우 다른 구현에서는 __new__초기화되지 않은 새 인스턴스를 반환하는지 여부를 알 수있는 일반적인 방법이 없기 때문에 new가 가장 바깥쪽에 있어야합니다. 아니.
khazhyk

1
@khazhyk 글쎄요, async def __init__(...)OP에서 보여준 것처럼 확실히 정의를 방해하는 무언가가 있습니다 . 그리고 저는 TypeError: __init__() should return None, not 'coroutine'예외가 파이썬 내부에서 하드 코딩되어 우회 할 수 없다고 믿습니다 . 그래서 어떻게 async def __new__(...)차이가 났는지 이해하려고 노력했습니다 . 이제 내 이해는 async def __new__(...)"만약 __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__대신, 즉 단지 클라이언트 오버라이드 (override)하는 것을하자)?
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.