这怎么是协程?

How is this a coroutine?

我正在尝试理解 Python 中的协同程序(以及一般情况)。一直在阅读理论、概念和一些示例,但我仍在苦苦挣扎。我了解异步模型(做了一点 Twisted)但还不了解协程。

一个 tutorial 将其作为协程示例给出(我做了一些更改来说明我的问题):

async def download_coroutine(url, number):
    """
    A coroutine to download the specified url
    """
    request = urllib.request.urlopen(url)
    filename = os.path.basename(url)
    print("Downloading %s" % url)

    with open(filename, 'wb') as file_handle:
        while True:
            print(number) # prints numbers to view progress
            chunk = request.read(1024)
            if not chunk:
                print("Finished")
                break
            file_handle.write(chunk)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

这是运行和这个

coroutines = [download_coroutine(url, number) for number, url in enumerate(urls)]
completed, pending = await asyncio.wait(coroutines)

查看生成器协程示例,我可以看到一些 yield 语句。这里什么都没有,urllib 是同步的,据我所知。

此外,由于代码应该是异步的,我希望看到一系列交错的数字。 (1, 4, 5, 1, 2, ..., "Finished", ...) 。我看到的是一个以 Finished 结尾的重复数字,然后是另一个数字 (3, 3, 3, 3, ... "Finished", 1, 1, 1, 1, .. ., "Finished" ...).

在这一点上我很想说教程是错误的,这是一个协程只是因为前面有异步。

co中的coroutine代表cooperative。让步(对其他例程)使例程成为协同例程,真的,因为只有在等待时让步才能交错其他协同例程。在 Python 3.5 及更高版本的新 async 世界中,这通常是通过 await-ing 来自其他协程的结果来实现的。

根据该定义,您找到的代码不是协程。就 Python 而言,它是一个协程 object,因为这是赋予使用 [= 创建的函数对象的类型14=].

所以是的,教程是..无用的,因为他们在协程函数中使用了完全同步的、不合作的代码。

需要一个异步 HTTP 库,而不是 urllib。点赞 aiohttp:

import aiohttp

async def download_coroutine(url):
    """
    A coroutine to download the specified url
    """
    filename = os.path.basename(url)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            with open(filename, 'wb') as fd:
                while True:
                    chunk = await resp.content.read(1024)
                    if not chunk:
                        break
                    fd.write(chunk)
    msg = 'Finished downloading {filename}'.format(filename=filename)
    return msg

在等待连接建立、等待更多网络数据以及再次关闭会话时,此协程可以让步给其他例程。

我们可以进一步使文件写入异步,但是 has portability issues; the aiofiles project 库使用线程来卸载阻塞调用。使用该库,代码需要更新为:

import aiofiles

async with aiofiles.open(filename, 'wb') as fd:
    while True:
        chunk = await resp.content.read(1024)
        if not chunk:
            break
        await fd.write(chunk)

注意:博客 post 已经更新以解决这些问题。