web-dev-qa-db-fra.com

Duplication de code pour les implémentations synchrones et asynchrones

Lorsque j'implémente des classes qui ont des utilisations dans des applications synchrones et asynchrones, je me retrouve à conserver un code pratiquement identique pour les deux cas d'utilisation.

À titre d'exemple, considérons:

from time import sleep
import asyncio


class UselessExample:
    def __init__(self, delay):
        self.delay = delay

    async def a_ticker(self, to):
        for i in range(to):
            yield i
            await asyncio.sleep(self.delay)

    def ticker(self, to):
        for i in range(to):
            yield i
            sleep(self.delay)


def func(ue):
    for value in ue.ticker(5):
        print(value)


async def a_func(ue):
    async for value in ue.a_ticker(5):
        print(value)


def main():
    ue = UselessExample(1)
    func(ue)
    loop = asyncio.get_event_loop()
    loop.run_until_complete(a_func(ue))


if __name__ == '__main__':
    main()

Dans cet exemple, ce n'est pas trop mal, les méthodes ticker de UselessExample sont faciles à maintenir en tandem, mais vous pouvez imaginer que la gestion des exceptions et des fonctionnalités plus compliquées peuvent rapidement développer une méthode et la faire plus un problème, même si les deux méthodes peuvent rester pratiquement identiques (en remplaçant uniquement certains éléments par leurs homologues asynchrones).

En supposant qu'il n'y a pas de différence substantielle qui vaut la peine d'être entièrement implémenté, quelle est la meilleure façon (et la plus Pythonic) de maintenir une classe comme celle-ci et d'éviter la duplication inutile?

20
Grismar

async/await Est contagieux de par sa conception.

Acceptez que votre code ait différents utilisateurs - synchrones et asynchrones, et que ces utilisateurs auront des exigences différentes, que, au fil du temps, les implémentations divergent.

Publier des bibliothèques distinctes

Par exemple, comparez aiohttp vs aiohttp-requests Vs requests.

De même, comparez asyncpg et psycopg2.

Comment s'y rendre

Opt1. (facile) la mise en œuvre du clone, leur permettre de diverger.

Opt2. (sensible) refactor partiel, par ex. la bibliothèque asynchrone dépend et importe la bibliothèque de synchronisation.

Opt3. (radical) créer une bibliothèque "pure" qui peut être utilisée à la fois dans le programme de synchronisation et asynchrone. Par exemple, voir https://github.com/python-hyper/hyper-h2 .

À la hausse, les tests sont plus faciles et approfondis. Considérez la difficulté (ou l'impossibilité) de forcer le framework de test à évaluer tous les ordres d'exécution simultanés possibles dans un programme asynchrone. La bibliothèque pure n'a pas besoin de ça :)

À la baisse, ce style de programmation nécessite une réflexion différente, n'est pas toujours simple et peut être sous-optimal. Par exemple, au lieu de await socket.read(2**20), vous écririez for event in fsm.Push(data): ... et compteriez sur l'utilisateur de votre bibliothèque pour vous fournir des données en morceaux de bonne taille.

Pour le contexte, voir l'argument backpressure dans https://vorpus.org/blog/some-oughtts-on-asynchronous-api-design-in-a-post-asyncawait-world/

0
Dima Tisnek