LoginSignup
8
12

More than 5 years have passed since last update.

pythonでJSのsetTimeoutみたいなこと

Last updated at Posted at 2017-07-06

というお題でいくつか試してみました。

同期処理

まず一番シンプルには次のように time.sleepを使えば指定した時間処理を遅延させることはできます。

import time
def hello(target):
    print("Hello {}".format(target))

time.sleep(3)
hello("World")

しかしこれでは time.sleep以降の処理すべてが遅延します。setTimeoutみたいに特定の処理だけ遅延させるにはどうすればよいのでしょうか?

非同期処理(スレッド)

threading.Timerを使えばできます。

from threading import Timer

def hello(target):
    print("Hello {}".format(target))

timer = Timer(3, hello, ("World", ))
timer.start()

以下のように途中でキャンセルもできます。

timer.cancel()

この方法ではTimerの呼び出しごとにスレッドを生成します。若干非効率ですし、JavaScriptのsetTimeoutの方法とは異なります。

非同期処理(asyncio)

JavaScriptは単一スレッドでイベントループがさまざまなイベントを処理しています。Python3.4以降でもそういう形で実行する方法があります。asyncioというPython3.4以降に標準添付されているパッケージを使う方法です。JavaScriptと同じくイベントループを使ってます。ただし、JavaScriptではイベントループは暗黙的なものでしたが、asyncioでは明示的に操作してあげる必要があります。

以下の2つがコード例です。123

やっていることはほぼ同じで違うAPIを用いた例を掲載しました。call_later版はasyncio.call_laterで指定した秒数後(以降に)指定した関数を実行するようなイベントを発生させてます。コルーチン版4は wrap_with_delayという指定秒数制御をイベントループにもどしてから元の関数を呼ぶコルーチンでラッピングしてます。今回のコードだとcall_later版の方が簡潔ですが、複数の処理が連鎖するような場合は同期処理のように書けるコルーチン版の方がわかりやすくなります。

call_later版

import asyncio

def hello(target, loop=None):
    print("Hello {}".format(target))
    if loop is None:
        loop = asyncio.get_event_loop()
    loop.stop()  # イベントループをとめて制御をもどす処理を追加

loop = asyncio.get_event_loop() # デフォルトのイベントループ取得
loop.call_later(3, hello, "World")
loop.run_forever() # イベントループスタート。明示的にstopしないと戻ってこない。
# loop.close()

コルーチン版

import asyncio

# asyncとつけると通常の関数ではなくコルーチン
async def wrap_with_delay(sec, func, *args):
    await asyncio.sleep(sec) # awaitで制御をイベントループにもどす
    func(*args)

def hello(target, loop=None):
    print("Hello {}".format(target))
    if loop is None:
        loop = asyncio.get_event_loop()
    loop.stop()  # イベントループをとめて制御をもどす処理を追加

loop = asyncio.get_event_loop()
asyncio.ensure_future(wrap_with_delay(3, hello, "World"))
loop.run_forever()
# loop.close()

  1. 本来イベントループは複数の処理を時分割して並列に実行するためのしくみなので、この例のように遅延実行のみイベントループにのせることはふつうしません。 

  2. イベントループをまわっしぱなしだと実行が終わらないので、簡単に試せるようにループをとめる処理を追加してあります。他の方法との比較という観点からは余計な処理です。 

  3. デフォルトのイベントループをクローズするとそれ以降 asyncio.get_event_loop がエラーになるので、jupyter notebookでの実行を考えてコメントアウトしてます。 

  4. このコードそのものはPython3.5以降で動作するがちょっと変えればPython3.4でも実行可能 

8
12
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
8
12