Python Tips: Python でエラー TypeError: object async_generator can't be used in 'await' expression を解消したい

Python で次のようなエラーが出たときの解消方法についてです。

TypeError: object async_generator can't be used in 'await' expression

確認時の Python バージョン

  • Python 3.12.0

エラー

次のコードで再現できます。

sample_async_generator.py:

import asyncio


async def main():
    numbers = await async_range(5)
    # or
    # numbers = [x for x in await async_range(5)]
    print(numbers)


async def async_range(n):
    for i in range(n):
        yield i
        await asyncio.sleep(0.01)


if __name__ == "__main__":
    asyncio.run(main())
❯ python sample_async_generator.py
Traceback (most recent call last):
  File "/path/to/file/sample_async_generator.py", line 16, in <module>
    asyncio.run(main())
  File "/path/to/runtime/python/3.12.0/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/path/to/runtime/python/3.12.0/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/runtime/python/3.12.0/lib/python3.12/asyncio/base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/<path>/sample_async_generator.py", line 5, in main
    numbers = await async_range(5)
              ^^^^^^^^^^^^^^^^^^^^
TypeError: object async_generator can't be used in 'await' expression

原因

まさにエラーメッセージのとおりですが、 async_generator オブジェクトには await が使えないことが原因です。

上のサンプルだと、 async def で定義されている async_range() の戻り値は coroutine オブジェクトではなく async_generator オブジェクトですが、それに対して await してしまっていることが問題です。

解決方法

async_generator オブジェクトには await ではなく async for を使います。

上のサンプルは次のように書き換えると正しく動きます。

sample_async_generator_fixed.py:

import asyncio


async def main():
    # `async for` を使う
    numbers = [x async for x in async_range(5)]
    print(numbers)
    # or
    async for number in async_range(5):
        print(number)
    # ↑ は ↓ のコードとほぼ等価
    iterator = async_range(5)
    running = True
    while running:
        try:
            number = await iterator.__anext__()
        except StopAsyncIteration:
            running = False
        else:
            print(number)


async def async_range(n):
    for i in range(n):
        yield i
        await asyncio.sleep(0.01)


if __name__ == "__main__":
    asyncio.run(main())

参考