Why can I only use the await keyword inside of asy

2020-02-11 07:11发布

问题:

Suppose I have code like this

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

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

Which is fine. But then I want to clean the data, so I do

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)

(I could clean text inside show_something(), but let's assume that show_something() can print many things and doesn't or shouldn't know the proper way of cleaning them.)

This is of course a SyntaxError: 'await' outside async function. But—if this code could run—while the await expression is not placed inside a coroutine function, it is executed in the context of one. Why this behavior is not allowed?

I see one pro in this design; in my latter example, you can't see that show_something()'s body is doing something that can result in its suspension. But if I were to make fetch_clean_text() a coroutine, not only would it complicate things but would probably also reduce performance. It just makes little sense to have another coroutine that doesn't perform any I/O by itself. Is there a better way?

回答1:

I see one pro in this design; in my latter example, you can't see that show_something()'s body is doing something that can result in its suspension.

That's exactly why it designed this way. Writing concurrent code can be very tricky and asyncio authors decided that it's critically important to always explicitly mark places of suspend in code.

This article explains it in details (you can start from "Get To The Point Already" paragraph).

But if I were to make fetch_clean_text() a coroutine, not only would it complicate things but would probably also reduce performance.

You need coroutines almost exclusively when you deal with I/O. I/O always takes much-much more time than overhead for using coroutines. So I guess it can be said - no, comparing to I/O you already deal with, you won't lose any significant amount of execution time for using coroutines.

Is there a better way?

Only way I can suggest: is to maximally split logic that deals with I/O (async part) from rest of the code (sync part).

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)