3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonのasyncioではネストした同期関数の中でawaitを呼ぶことはできない

Posted at

Pythonで以下の様な処理を考える。
f1は引数として関数を受け取り、文字列をprintしたあとにその関数を実行する。

(Python3.7.0で確認)

import time

def f1(termination):
    print("[f1] start")
    termination()
    print("[f1] done")

def main():
    f1(lambda: time.sleep(1.0) )

main()

# 実行結果
# [f1] start
#  ... 1秒まつ ...
# [f1] done

さて、この処理をasyncioを使って書き直してみる。次の様になる。

import time,asyncio

def f1(termination):
    print("[f1] start")
    termination()
    print("[f1] done")

async def main():
    f1(lambda: time.sleep(1.0) )

asyncio.run(main())

ここでの変更点は

  • main, f1の定義に async をつけ、coroutineを返す関数(coroutine function)にする。
  • main()を呼ぶ部分をasyncio.run(main()) で置き換える

の2つである。最初のコードと全く同じ様に動作する。

さて、ここでf1に引数で渡している関数の中でawaitしたいとしよう。
実は次のように書くとうまくいかない。

import time,asyncio

def f1(termination):
    print("[f1] start")
    termination()
    print("[f1] done")

async def main():
    f1(lambda: await asyncio.sleep(1.0) )

asyncio.run(main())
  File "nest2.py", line 9
    f1(lambda: await asyncio.sleep(1.0) )
              ^
SyntaxError: 'await' outside async function

これは、awaitを行なっているのが同期関数(普通のdefで定義された関数)の中だから。これを適切に動かすためには、以下の様にf1もcoroutineにする必要がある。

import time,asyncio

async def f1(callback):      # asyncをつけた
    print("[f1] start")
    await callback()         # awaitをつけた
    print("[f1] done")

async def main():            # asyncをつけた
    async def callback():
        await asyncio.sleep(1.0)
    await f1( callback )     # awaitをつけた

asyncio.run(main())

f1が通常の(同期)関数の場合にはどんなに頑張ってもその配下でawaitを呼ぶことはPythonの文法的に不可能となっている。

mainはcoroutineで、f1はその中でしか呼ばれないので必ずcoroutine内で呼ばれているのであるが、defを使った処理の中ではawaitを呼ぶことはできない。

この文法が問題になるケース

実はこの文法が問題になるケースがある。
上記のような簡単な場合は、おとなしくf1にもasyncをつければいいだけだが、f1が何重にもネストしているようなライブラリの関数だったとする。
その場合、ネストしている関数すべてにasyncをつけて、その呼び出し部分すべてにawaitをつけなくてはいけない。(例えば"main"->"f1"->"f2"-> .... -> callback と呼ばれる様な場合は、ネストしているすべての関数の変更が必要。)

(雑感)awaitがあるときにネストした先からイベントループに処理を戻せばよいので、処理系の実装上はできても良さそうなものだが、Pythonの文法をデザインする段階でこういう方針が取られたようだ。残念。javascriptの場合はPromise()が帰ってくるだけなのでネストした先でもawaitできる(と思う)

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?