it-swarm.dev

Jak mohu čekat v objektu __await__ budoucnosti?

PEP 0492 přidává novou __await__ magickou metodu. Objekt, který implementuje tuto metodu, se stává objektem budoucnost a lze jej očekávat pomocí await. To je jasné:

import asyncio


class Waiting:
    def __await__(self):
        yield from asyncio.sleep(2)
        print('ok')

async def main():
    await Waiting()

if __== "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Ok, ale co když chci zavolat nějakou async def definovanou funkci namísto asyncio.sleep? Nemohu použít await, protože __await__ není async funkce, nemohu použít yield from, protože nativní coroutines vyžadují await výraz:

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        yield from new_sleep()  # this is TypeError
        await new_sleep()  # this is SyntaxError
        print('ok')

Jak to mohu vyřešit?

25
Mikhail Gerasimov

Použít přímé volání __await__():

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        return new_sleep().__await__()

Řešení doporučil Yury Selivanov (autor PEP 492 ) pro knihovnu aioodbc

28
Andrew Svetlov

Krátká verze: await foo může být nahrazena yield from foo.__await__()


Kombinace všech myšlenek z ostatních odpovědí -

v nejjednodušším případě, pouze delegování na další čekající díla:

def __await__(self):
    return new_sleep().__await__()

To funguje, protože metoda __await__ vrací iterátor (viz PEP 492 ), takže vrácení jiného iterátoru __await__ je v pořádku.

To samozřejmě znamená, že nemůžeme změnit chování zavěšení originálu, které je vůbec možné očekávat. Obecnějším přístupem je zrcadlení klíčového slova await a použití yield from - to nám umožňuje kombinovat více iterátorů čekajících na čekání do jednoho:

def __await__(self):
    # theoretically possible, but not useful for my example:
    #yield from something_else_first().__await__()
    yield from new_sleep().__await__()

Tady je úlovek: to nedělá přesně to samé jako první varianta! yield from je výraz, takže k tomu, abychom to udělali přesně stejně jako dříve, musíme také vrátit tuto hodnotu:

def __await__(self):
    return (yield from new_sleep().__await__())

To přímo odráží, jak bychom měli psát správné delegování pomocí syntaxe await:

    return await new_sleep()

extra bit - jaký je rozdíl mezi těmito dvěma?

def __await__(self):
    do_something_synchronously()
    return new_sleep().__await__()

def __await__(self):
    do_something_synchronously()
    return (yield from new_sleep().__await__())

První varianta je prostá funkce: když ji zavoláte, provede se do_... a vrátí se iterátor. Druhou je funkce generátoru; volání to nevykonává žádný náš kód vůbec! do_... bude provedeno pouze tehdy, když je vrácený iterátor poprvé vydán. To je rozdíl v následujících, poněkud nepříznivých situacích:

def foo():
    tmp = Waiting.__await__()
    do_something()
    yield from tmp
11
Silly Freak

Nechápal jsem, proč nemohu výnos z nativní coroutine uvnitř __await__, ale vypadá to, že je možné výnos z generátoru coroutine inside __await__ a výnos z nativního coututinu uvnitř toho generátor coroutine . Funguje to:

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        @asyncio.coroutine
        def wrapper(coro):
            return (yield from coro)
        return (yield from wrapper(new_sleep()))
4
Mikhail Gerasimov

Chcete-li čekat uvnitř funkce __await__, použijte následující kód:

async def new_sleep():
    await asyncio.sleep(1)


class Waiting:
    def __await__(self):
        yield from new_sleep().__await__()
        print('first sleep')
        yield from new_sleep().__await__()
        print('second sleep')
        return 'done'
4
crvv

Použijte dekoratér.

def chain__await__(f):
    return lambda *args, **kwargs: f(*args, **kwargs).__await__()

Pak napište __await__ jako nativní coututinu.

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    @chain__await__
    async def __await__(self):
        return await new_sleep()
0
Huazuo Gao

Můžete také zjednodušit Mikhailovu verzi:

async def new_sleep():
    await asyncio.sleep(2)

class Waiting:
    def __await__(self):
        async def wrapper():
            await new_sleep()
            print("OK")
        return wrapper()
0
Qeek