web-dev-qa-db-fra.com

Pourquoi ne puis-je utiliser que le mot-clé attendent à l'intérieur de la fonction asynchrone?

Supposons que j'ai un code comme celui-ci

async def fetch_text() -> str:
    return "text "

async def show_something():
    something = await fetch_text()
    print(something)

Ce qui est bien. Mais alors je veux nettoyer les données, donc je le fais

async def fetch_text() -> str:
    return "text "

def fetch_clean_text(text: str) -> str:
    text = await fetch_text()
    return text.strip(text)

async def show_something():
    something = fetch_clean_text()
    print(something)

(Je pourrais nettoyer le texte à l'intérieur de show_something(), mais supposons que show_something() puisse imprimer beaucoup de choses et ne sait pas ou ne devrait pas savoir comment les nettoyer.)

Il s'agit bien sûr d'un SyntaxError: 'await' outside async function. Mais - si ce code pouvait s'exécuter - alors que l'expression await n'est pas placée dans une fonction coroutine, elle est exécutée dans le contexte de celle-ci. Pourquoi ce comportement n'est pas autorisé?

Je vois un pro dans cette conception; dans mon dernier exemple, vous ne pouvez pas voir que le corps de show_something() fait quelque chose qui peut entraîner sa suspension. Mais si je devais faire de fetch_clean_text() une coroutine, non seulement cela compliquerait les choses, mais cela réduirait probablement aussi les performances. Cela n'a tout simplement aucun sens d'avoir une autre coroutine qui n'effectue aucune E/S par elle-même. Y a-t-il une meilleure façon?

4
squirrel

Je vois un pro dans cette conception; dans mon dernier exemple, vous ne pouvez pas voir que le corps de show_something () fait quelque chose qui peut entraîner sa suspension.

C'est exactement pourquoi il a conçu de cette façon. L'écriture de code simultané peut être très délicate et les auteurs asynchrones ont décidé qu'il était extrêmement important de toujours marquer explicitement les lieux de suspension dans le code.

Cet article l'explique en détail (vous pouvez commencer à partir du paragraphe "Get To The Point Déjà").

Mais si je devais faire de fetch_clean_text () une coroutine, non seulement cela compliquerait les choses, mais cela réduirait probablement aussi les performances.

Vous avez besoin de coroutines presque exclusivement lorsque vous traitez des E/S. Les E/S prennent toujours beaucoup plus de temps que les frais généraux pour utiliser des coroutines. Donc, je suppose que cela peut être dit - non, par rapport aux E/S que vous traitez déjà, vous ne perdrez pas beaucoup de temps d'exécution pour l'utilisation des coroutines.

Y a-t-il une meilleure façon?

La seule façon que je puisse suggérer est de séparer au maximum la logique qui traite les E/S (partie asynchrone) du reste du code (partie sync).

from typing import Awaitable

def clean_text(text: str) -> str:
    return text.strip(text)

async def fetch_text() -> Awaitable[str]:
    return "text "

async def fetch_clean_text(text: str) -> Awaitable[str]:
    text = await fetch_text()
    return clean_text(text)

async def show_something():
    something = await fetch_clean_text()
    print(something)
5
Mikhail Gerasimov