python3.6新变化'async for'与枚举不兼容吗

Posted

技术标签:

【中文标题】python3.6新变化\'async for\'与枚举不兼容吗【英文标题】:Is python3.6 new change 'async for' not compatible with enumeratepython3.6新变化'async for'与枚举不兼容吗 【发布时间】:2018-01-03 03:43:33 【问题描述】:

我正在尝试将我的应用程序从 python2.7 移动到 python3.6,用于 asyncio 和关联库,但我发现某些功能无法正常工作,但我除外。 我使用电机从 mongodb 异步查询,例如:

async def do_query():
    song_already_processed = set()
    song_table = db.song_table
    async for index, item in enumerate(song_table.find('lang': 'English',
                              'id': 1, '_id': 0, 'title': 1, 'artist.name': 1)):
        if index > 100:
            break
        if item['id'] in song_already_processed:
            continue
        song_already_processed.add(item['id'])

    print(len(song_already_processed))

但它引发了错误:

TypeError: 'AsyncIOMotorCursor' object is not iterable

我认为异步迭代器协议与普通的迭代器协议不同,所以枚举不能很好地工作。有我可以使用的异步枚举吗?

顺便说一句,我知道很多方法可以获取 100 个文档并停止迭代,我只是想知道如何正确使用“async for”

【问题讨论】:

song_table.find 只是返回一个游标对象,为什么你认为它可以被迭代? @Sraw TBF,光标似乎是“可迭代的”。当然看起来和.. 异步迭代器和迭代器是两个不同的东西,围绕一个功能构建的工具可能不适用于另一个。据我所知,aenumerate 还没有正式的实现,但您可以查看this 以了解其结构 【参考方案1】:

asyncstdlib library(免责声明:我维护这个包)提供了标准库助手的async 变体。具体来说,asyncstdlib.enumerate 的工作方式与 enumerate 类似,但需要并产生一个异步迭代。

import asyncstdlib as a

async for index, item in a.enumerate(song_table.find(...)):
    if index > 100:
        break
    ...

请注意,将break 退出异步迭代通常不是一个好主意——迭代器可能不会在迭代结束时进行清理(请参阅PEP 533 for details)。

由于您使用index 只是为了获取前100 个项目,因此您也可以使用asyncstdlib.islice 直接安全地限制迭代:

import asyncstdlib as a

async for item in a.islice(song_table.find(...), 100):
    ...

Python3.6新增asynchronous generators,方便实现异步枚举:

async def aenumerate(asequence, start=0):
    """Asynchronously enumerate an async iterator from a given start value"""
    n = start
    async for elem in asequence:
        yield n, elem
        n += 1

对于旧版本的 Python,必须手动修改异步生成器:

class AsyncEnumerate:
    """Asynchronously enumerate an async iterator from a given start value"""
    def __init__(self, asequence, start=0):
        self._asequence = asequence
        self._value = start

    async def __anext__(self):
        elem = await self._asequence.__anext__()
        value, self._value = self._value, self._value + 1
        return value, elem

    def __aiter__(self):
        return self

【讨论】:

【参考方案2】:

如果你不介意有外部依赖,可以使用aiostream.stream.enumerate:

async for i, item in aiostream.stream.enumerate(cursor):
    ...

在demonstration 和documentation 中查看更多示例。

【讨论】:

AttributeError: module 'aiostream' has no attribute 'enumerate' aiostream.enumerate => aiostream.stream.enumerate FTFY【参考方案3】:

我有一个类似的问题,涉及将异步生成器用作使用枚举和异步 for 的可迭代对象。我意识到在我的情况下最简单的答案是通过修改 async generator 来返回索引,而不是 async for。见下文:

async def whitespace(data: List) -> tuple:
    """function to remove any whitespace in returned values"""
    for i, v in enumerate(data):
        yield i, x.strip()

async for i, x in whitespace(a_list_of_stuff):
    a_list_of_stuff[i] = x 

【讨论】:

以上是关于python3.6新变化'async for'与枚举不兼容吗的主要内容,如果未能解决你的问题,请参考以下文章

python3.6,async with和await的区别

Python 3.6.8 - multiprocessing.Pool.apply_async() 不工作

在 Python2.7 中应用 Async

活动期间 GUI 没有变化? (Python3.6,PyQt5)

python3.6 for pygame安装

python之async-timeout模块