web-dev-qa-db-fra.com

Conversion d'une fonction Python avec rappel en asyncio attendable

Je veux utiliser la bibliothèque PyAudio dans un contexte asynchrone, mais le point d'entrée principal de la bibliothèque n'a qu'une API basée sur le rappel:

import pyaudio

def callback(in_data, frame_count, time_info, status):
    # Do something with data

pa = pyaudio.PyAudio()
self.stream = self.pa.open(
    stream_callback=callback
)

Comment j'espère l'utiliser est quelque chose comme ceci:

pa = SOME_ASYNC_COROUTINE()
async def listen():
    async for block in pa:
        # Do something with block

Le problème est que je ne sais pas comment convertir cette syntaxe de rappel en un futur qui se termine lorsque le rappel se déclenche. En JavaScript, j'utiliserais promise.promisify() , mais Python ne semble pas avoir quelque chose comme ça.

10
Migwell

Vous pouvez utiliser un Future

classe asyncio.Future (*, loop = None) ¶

Un avenir représente un résultat éventuel d'une opération asynchrone. Pas thread-safe.

L'avenir est un objet attendu. Les coroutines peuvent attendre sur les objets Future jusqu'à ce qu'elles aient un résultat ou un ensemble d'exceptions, ou jusqu'à ce qu'elles soient annulées.

En règle générale, les Futures sont utilisés pour activer le code bas niveau basé sur le rappel (par exemple dans les protocoles implémentés à l'aide de transports asynchrones) pour interagir avec le code asynchrone/attente de haut niveau .

La règle générale est de ne jamais exposer les objets Future dans les API utilisateur, et la méthode recommandée pour créer un objet Future consiste à appeler loop.create_future (). De cette façon, les implémentations alternatives de boucle d'événements peuvent injecter leurs propres implémentations optimisées d'un objet Future.

Un exemple idiot:

def my_func(loop):
    fut = loop.create_future()
    pa.open(
        stream_callback=lambda *a, **kw: fut.set_result([a, kw])
    )
    return fut


async def main(loop):
    result = await my_func(loop)  # returns a list with args and kwargs 

Je suppose que pa.open s'exécute dans un thread ou un sous-processus. Sinon, vous devrez peut-être également encapsuler l'appel à open avec asyncio.loop.run_in_executor

1
Oleksandr Fedorov