__Init__ में प्रतीक्षा के साथ वर्ग विशेषता कैसे सेट करें


95

मैं 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 / प्रतीक्षा की वायरल प्रकृति के कारण काम नहीं करेगा, लेकिन क्या आपने एक async फ़ंक्शन को परिभाषित करने _pool_init(dsn)और फिर इसे कॉल करने का प्रयास किया है __init__? यह init-in-constructor उपस्थिति को संरक्षित करेगा।
एपी

1
यदि आप curio का उपयोग करते हैं: curio.readthedocs.io/en/latest/…
matsjoyce

1
उपयोग @classmethodaltern यह एक वैकल्पिक निर्माता है। वहाँ async काम रखो; उसके बाद __init__, बस selfविशेषताओं को निर्धारित करें
ग्रैजाइटिस

जवाबों:


120

अधिकांश जादू तरीकों के साथ काम करने के लिए डिज़ाइन नहीं कर रहे हैं 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__मुद्दे को संबोधित करता है। मैंने वर्ग चर नहीं देखे हैं जो जंगली में अतुल्यकालिक कॉल करता है जिसे मैं याद कर सकता हूं, इसलिए मुझे नहीं पता कि कोई अच्छी तरह से स्थापित पैटर्न उभरा है।


38

ऐसा करने का एक और तरीका है, मौज-मस्ती के लिए:

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
यह वर्तमान में मेरी राय में सबसे स्पष्ट और समझने योग्य कार्यान्वयन है। मुझे वास्तव में पसंद है कि यह कितना सहज रूप से एक्स्टेंसिबल है। मैं चिंतित था कि मेटाक्लासेस में तल्लीन करना आवश्यक होगा।
टंकोबोट

1
__init__यदि super().__new__(cls)पहले से मौजूद उदाहरण लौटाता है, तो इसका सही शब्दार्थ नहीं है - सामान्य तौर पर, यह छोड़ देता है __init__, लेकिन आपका कोड नहीं।
एरिक

हम्म, प्रति object.__new__प्रलेखन, __init__केवल अगर आह्वान किया जाना चाहिए isinstance(instance, cls)? यह मुझे कुछ अस्पष्ट लगता है ... लेकिन मुझे आपके द्वारा कहीं भी दावा करने वाले शब्दार्थ नहीं
दिखते

इस बारे में अधिक सोचना, यदि आप __new__पहले से मौजूद वस्तु को वापस करने के लिए ओवरराइड करते हैं, तो उस नए को किसी भी तरह से बनाने के लिए सबसे बाहरी होने की आवश्यकता होगी, क्योंकि अन्य कार्यान्वयनों __new__को जानने का कोई सामान्य तरीका नहीं होगा यदि आप एक नया असंबद्ध उदाहरण वापस कर रहे हैं या नहीं।
khazhyk

1
@khazhyk खैर, वहाँ निश्चित रूप से कुछ परिभाषित करने से रोकता है async def __init__(...), जैसा कि ओपी द्वारा दिखाया गया है, और मेरा मानना ​​है कि TypeError: __init__() should return None, not 'coroutine'अपवाद पायथन के अंदर हार्डकोड है और इसे बाईपास नहीं किया जा सकता है। इसलिए मैंने यह समझने की कोशिश की कि कैसे async def __new__(...)एक अंतर बना। अब मेरी समझ यह है कि, आपके async def __new__(...)(ab) "अगर __new__()cls का एक उदाहरण नहीं लौटाता है, तो वापस नहीं आएगा " की विशेषता का उपयोग करें __init__()। आपका नया __new__()एक coroutine देता है, कोई cls नहीं। इसीलिए। चतुर हैक!
रायलूओ

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



0

अपनी आवश्यकताओं के आधार पर, आप इसका भी उपयोग कर सकते हैं AwaitLoader: https://pypi.org/project/async-property/

डॉक्स से:

AwaitLoaderinstance.load()संपत्तियों को लोड करने से पहले, मौजूद होने का इंतजार करेंगे ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.