- Python
Python time.sleepの使い方|秒・ミリ秒の待機と一定間隔実行
time.sleepとは、呼び出したスレッドの処理を指定した秒数だけ止めるPython標準ライブラリの関数です。 import time のあとに time.sleep(1) と書けば1秒、time.sleep(0.5) と書けば0.5秒(500ミリ秒)待ちます。引数の単位は常に「秒」で、ミリ秒やマイクロ秒は小数で表します。
この記事では、単位の換算表から始めて、perf_counterで測った実際の待機時間、whileとsleepだけで作ったループが少しずつずれていく様子、time.monotonicで補正して一定間隔を保つループ、Ctrl+Cでの中断、asyncio.sleepやEvent.waitとの使い分けまでを、すべて実行結果つきで説明します。実行結果は Python 3.11.15(Linux)で実際に動かしたものです。WindowsとmacOSについては手元で動かしていないため、その箇所には「未実行」と明記しています。
time.sleepの早見表
やりたいことから書き方を探せる表です。コードはすべて import time が済んでいる前提です。
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| 1秒待つ | time.sleep(1) |
引数の単位は秒 |
| 0.5秒(500ミリ秒)待つ | time.sleep(0.5) |
小数で指定 |
| 100ミリ秒待つ | time.sleep(0.1) |
time.sleep(100 / 1000) でも同じ |
| 1ミリ秒待つ | time.sleep(0.001) |
実測では約1.16ミリ秒(後述) |
| 1分待つ | time.sleep(60) |
time.sleep(1 * 60) と書くと意図が伝わる |
| 1分30秒待つ | time.sleep(timedelta(minutes=1, seconds=30).total_seconds()) |
結果は90.0 |
| 待った時間を測る | t = time.perf_counter() → 待機 → time.perf_counter() - t |
時間の計測は perf_counter |
| 一定間隔で繰り返す | 次の時刻を time.monotonic() で持ち、差分だけ待つ |
「ずれない一定間隔ループ」の節 |
| Ctrl+Cで中断する | try: ... except KeyboardInterrupt: |
sleepの途中でもすぐ止まる |
| 別スレッドから待機を止める | threading.Event().wait(秒) |
set() で即座に待機が終わる |
| 非同期処理の中で待つ | await asyncio.sleep(1) |
async関数の中では time.sleep の代わりに使う |
| 数秒後に1回だけ実行 | threading.Timer(3, func).start() |
待つ間もメインの処理が進む |
| 「10分ごと」「毎日9時」に実行 | schedule.every(10).minutes.do(job) |
外部ライブラリ schedule |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python time.sleepの使い方|秒・ミリ秒の待機と一定間隔実行」
time.sleepの基本の書き方
いちばん短い例
import time
print(time.strftime("%H:%M:%S"))
time.sleep(2)
print(time.strftime("%H:%M:%S"))
実行結果:
05:23:30
05:23:32
1行目の出力から2秒後に2行目が出力されます。 sleepが止めるのは「呼び出したスレッド」だけで、戻り値はありません(Noneが返ります)。
import の書き方は2通り
| 書き方 | 呼び出し方 | 向いている場面 |
|---|---|---|
import time |
time.sleep(1) |
time.perf_counter なども合わせて使うとき。どのモジュールの関数かが読み取りやすい |
from time import sleep |
sleep(1) |
sleepだけを何度も使う短いスクリプト |
import time と書いたのに sleep(1) と呼ぶと、次のエラーになります。
NameError: name 'sleep' is not defined
この場合は time.sleep(1) と書くか、from time import sleep に変えると解消します。なお、自分のファイル名を time.py にすると、標準ライブラリではなくそのファイルが読み込まれてしまいます。ファイル名は wait_sample.py のように別の名前にしておくと安心です。
引数に渡せる値と、渡せない値
| 渡した値 | 結果(Python 3.11.15で実行) |
|---|---|
time.sleep(1) |
1秒待つ |
time.sleep(0.25) |
0.25秒待つ(計測値 0.250秒) |
time.sleep(0) |
待たずにすぐ戻る(計測値 約0.06ミリ秒) |
time.sleep(-1) |
ValueError: sleep length must be non-negative |
time.sleep("1") |
TypeError: 'str' object cannot be interpreted as an integer |
time.sleep(None) |
TypeError: 'NoneType' object cannot be interpreted as an integer |
input() や設定ファイルから読んだ値は文字列なので、float() で数値に直してから渡します。 計算で求めた待ち時間がマイナスになる可能性があるときは、time.sleep(max(0.0, 待ち時間)) と書くと ValueError を避けられます。この書き方は後半の一定間隔ループでも使います。
秒・ミリ秒・マイクロ秒・分の指定方法(換算表)
time.sleepには「ミリ秒で指定する引数」はありません。すべて秒に換算して渡します。
| 待ちたい時間 | 秒に直した値 | 書き方 | 計算式で書く場合 |
|---|---|---|---|
| 1マイクロ秒 | 0.000001 | time.sleep(0.000001) |
time.sleep(1 / 1_000_000) |
| 100マイクロ秒 | 0.0001 | time.sleep(0.0001) |
time.sleep(100 / 1_000_000) |
| 1ミリ秒 | 0.001 | time.sleep(0.001) |
time.sleep(1 / 1000) |
| 10ミリ秒 | 0.01 | time.sleep(0.01) |
time.sleep(10 / 1000) |
| 100ミリ秒 | 0.1 | time.sleep(0.1) |
time.sleep(100 / 1000) |
| 500ミリ秒 | 0.5 | time.sleep(0.5) |
time.sleep(500 / 1000) |
| 1秒 | 1 | time.sleep(1) |
― |
| 1分 | 60 | time.sleep(60) |
time.sleep(1 * 60) |
| 5分 | 300 | time.sleep(300) |
time.sleep(5 * 60) |
| 1時間 | 3600 | time.sleep(3600) |
time.sleep(60 * 60) |
1_000_000 のアンダースコアは桁区切りで、1000000 と同じ値です。
ミリ秒で指定できる関数を自分で用意する
ミリ秒で考えることが多いなら、小さな関数を1つ作っておくと読みやすくなります。
import time
def sleep_ms(ms):
"""ミリ秒で指定して待つ"""
time.sleep(ms / 1000)
sleep_ms(250) # 250ミリ秒待つ
分・時間は timedelta で書くと読みやすい
import time
from datetime import timedelta
wait = timedelta(minutes=1, seconds=30).total_seconds()
print(wait) # 90.0
time.sleep(wait)
print(wait) の結果は 90.0 でした。「何分何秒」をそのまま書けるので、あとから読んだ人が電卓を使わずに済みます。
実際に何秒待つのか(perf_counterで実測)
測り方
待機時間を測るときは time.time() ではなく time.perf_counter() を使います。perf_counterは短い時間の計測向けに用意された時計で、止まっている間(sleep中)の時間も含めて数えます。
import time
import statistics
for req in (1, 0.5, 0.1, 0.01, 0.001, 0.0001, 0.000001, 0):
results = []
n = 5 if req >= 0.5 else 200 # 長い待機は5回、短い待機は200回測る
for _ in range(n):
t = time.perf_counter()
time.sleep(req)
results.append(time.perf_counter() - t)
print(f"指定={req * 1000:>9.4f}ms "
f"平均={statistics.mean(results) * 1000:9.4f}ms "
f"最小={min(results) * 1000:9.4f}ms "
f"最大={max(results) * 1000:9.4f}ms")
Linuxでの実測結果
Python 3.11.15、Linux(カーネル 6.18)のコンテナ環境で実行した結果を表にまとめました。ほかの処理も動いている共有マシンでの値です。
| 指定した時間 | 平均 | 最小 | 最大 | 平均の超過分 |
|---|---|---|---|---|
| 1秒(1000ms) | 1000.1292ms | 1000.1020ms | 1000.1558ms | 約0.13ms |
| 0.5秒(500ms) | 500.1434ms | 500.0819ms | 500.2180ms | 約0.14ms |
| 0.1秒(100ms) | 100.1763ms | 100.0498ms | 103.3885ms | 約0.18ms |
| 0.01秒(10ms) | 10.2338ms | 10.0443ms | 16.2320ms | 約0.23ms |
| 0.001秒(1ms) | 1.1554ms | 1.0368ms | 3.7449ms | 約0.16ms |
| 0.0001秒(100マイクロ秒) | 0.1683ms | 0.1451ms | 0.2431ms | 約0.07ms |
| 0.000001秒(1マイクロ秒) | 0.0648ms | 0.0547ms | 0.1669ms | 約0.06ms |
| 0 | 0.0621ms | 0.0333ms | 0.0839ms | 約0.06ms |
この結果から読み取れることは4つあります。
- 指定より短く終わることはなく、必ず少し長くなります。 すべての行で最小値が指定値を上回っています。
- 超過分は指定時間に関係なく、おおよそ0.06〜0.23ミリ秒でした。 1秒待っても1ミリ秒待っても、余分にかかる時間はほぼ同じ大きさです。
- 1マイクロ秒を指定しても、実際は約0.06ミリ秒(約60マイクロ秒)かかりました。 この環境では、sleepを呼び出して戻ってくるだけでその程度の時間がかかるためです。
- 最大値はときどき大きく伸びます。 10ミリ秒指定で16ミリ秒かかった回がありました。ほかの処理にCPUが使われていると、起きるのが遅れます。
Python公式ドキュメントのtime.sleepの説明にも、「システム内のほかの処理のスケジューリングにより、停止時間は要求より任意の長さだけ長くなることがある」という趣旨の記述があります(今回ページを開いて確認しました)。time.sleepは「少なくともこの時間は待つ」関数だと理解しておくと、設計で迷いません。
OS別の仕組みと精度(Windows・macOS・Linux)
OSごとの違いを表にまとめます。「仕組み」の列はPython公式ドキュメント(time.sleep の項)を今回開いて確認した内容です。実測値があるのはLinuxだけで、WindowsとmacOSは未実行です。
| OS | Python 3.11以降の仕組み(公式ドキュメントの記述) | 公式に書かれた分解能 | 1ミリ秒指定の実測 |
|---|---|---|---|
| Linux | clock_nanosleep() を使用(使える場合) |
1ナノ秒 | 平均1.1554ms(今回実行) |
| macOS | clock_nanosleep() がなければ nanosleep() を使用 |
1ナノ秒 | 未実行 |
| Windows 10以降 | 高分解能タイマー(waitable timer)を使用 | 100ナノ秒 | 未実行 |
| 上記が使えないUnix系 | select() を使用 |
1マイクロ秒 | 未実行 |
表の「分解能」は指定できる細かさのことで、「その精度で必ず起きる」という意味ではありません。上のLinuxの実測でも、分解能は1ナノ秒とされている一方、実際の超過分は0.06〜0.23ミリ秒ありました。
Python 3.11で変わったこと
公式ドキュメントには「バージョン3.11で変更」として、次の2点が書かれています。
| OS | 3.11より前 | 3.11以降 |
|---|---|---|
| Unix系(Linux・macOS) | 主に select() を使用(分解能1マイクロ秒) |
clock_nanosleep() または nanosleep() を使用(分解能1ナノ秒) |
| Windows | システムのタイマー間隔に合わせて起きる方式 | 高分解能のwaitable timerを使用(Windows 10以降で分解能100ナノ秒) |
Windowsでは、3.11より前のPythonで1ミリ秒を指定すると、タイマーの刻み(一般に約15.6ミリ秒と言われます)の影響で十数ミリ秒かかることがある、と広く知られています。これは今回手元では動かしていない内容(未実行)です。Windowsで数ミリ秒単位の待機を扱うなら、Python 3.11以降を使ったうえで、上の計測コードを自分のPCで走らせて確かめるのが確実です。計測コードはOSを問わずそのまま動きます。
もっと正確に待ちたいとき
sleepで手前まで待ち、残りをループで埋める方法があります。
import time
def precise_sleep(sec, margin=0.002):
"""終了時刻の少し手前まで sleep し、残りはループで時刻を見張る"""
end = time.perf_counter() + sec
if sec > margin:
time.sleep(sec - margin)
while time.perf_counter() < end:
pass
10ミリ秒指定を200回ずつ測った結果は次のとおりでした。
time.sleep 10ms指定 平均=10.1876ms 最大=13.6314ms
precise_sleep 10ms指定 平均=10.1213ms 最大=13.9885ms
平均は少し指定値に近づきましたが、今回の共有環境では最大値は変わりませんでした。 ループで見張っている間もCPUをほかの処理に譲る場面があるためです。この方法は待っている間CPUを使い続けるので、効果があるかどうかを自分の環境で測ってから採り入れるのがよいでしょう。多くの用途では、次に紹介する「ずれを溜めない書き方」のほうが役に立ちます。
whileとsleepだけの定期実行は少しずつずれる
「0.1秒ごとに処理を実行したい」と考えて、次のように書くことがよくあります。
import time
def work():
time.sleep(0.03) # 0.03秒かかる処理の代わり
start = time.perf_counter()
for i in range(1, 11):
work()
print(f"{i:2d}回目 {time.perf_counter() - start:6.3f}秒")
time.sleep(0.1)
実行結果:
1回目 0.030秒
2回目 0.160秒
3回目 0.291秒
4回目 0.421秒
5回目 0.551秒
6回目 0.683秒
7回目 0.813秒
8回目 0.944秒
9回目 1.074秒
10回目 1.204秒
0.1秒間隔のつもりが、実際は約0.13秒間隔になっています。 10回目は0.93秒付近に来てほしいところ、1.204秒でした。理由は単純で、1周にかかる時間が「処理の時間(0.03秒)+sleepの時間(0.1秒)+sleepの超過分」になるからです。
| 1周の内訳 | 時間 |
|---|---|
| work() の処理時間 | 約0.030秒 |
| time.sleep(0.1) | 約0.100秒 |
| sleepの超過分・printなど | 約0.0003秒 |
| 合計(=実際の間隔) | 約0.130秒 |
処理時間が毎回変わる場合は、間隔もそのたびに変わります。1時間、1日と動かし続けると、ずれは積み重なっていきます。
ずれない一定間隔ループ(time.monotonicで補正)
基本形:次に実行する時刻を持っておく
ずれを溜めないコツは、「何秒待つか」ではなく「次はいつ実行するか」を変数で持つことです。
import time
INTERVAL = 0.1 # 実行間隔(秒)
def work():
time.sleep(0.03) # 0.03秒かかる処理の代わり
start = time.perf_counter()
next_time = time.monotonic()
for i in range(1, 11):
work()
print(f"{i:2d}回目 {time.perf_counter() - start:6.3f}秒")
next_time += INTERVAL
time.sleep(max(0.0, next_time - time.monotonic()))
実行結果:
1回目 0.034秒
2回目 0.130秒
3回目 0.234秒
4回目 0.330秒
5回目 0.430秒
6回目 0.531秒
7回目 0.632秒
8回目 0.730秒
9回目 0.833秒
10回目 0.930秒
間隔が0.1秒に揃い、10回目は0.930秒でした。 補正なしの1.204秒と比べると、ずれが溜まっていないことがわかります。2つの結果を並べます。
| 回 | 補正なし | 補正あり | 理想(0.03+0.1×(回−1)) |
|---|---|---|---|
| 1回目 | 0.030秒 | 0.034秒 | 0.030秒 |
| 4回目 | 0.421秒 | 0.330秒 | 0.330秒 |
| 7回目 | 0.813秒 | 0.632秒 | 0.630秒 |
| 10回目 | 1.204秒 | 0.930秒 | 0.930秒 |
ポイントは次の3行です。
| 行 | 役割 |
|---|---|
next_time = time.monotonic() |
基準になる時刻を最初に1回だけ取る |
next_time += INTERVAL |
次の実行時刻を「前回の予定時刻+間隔」で決める。実際に終わった時刻ではなく予定時刻に足すので、ずれが次の周に持ち越されない |
time.sleep(max(0.0, next_time - time.monotonic())) |
予定時刻までの残り時間だけ待つ。残りがマイナスなら0にして ValueError を避ける |
なぜ time.time() ではなく time.monotonic() なのか
| 関数 | 何を返すか | 時刻合わせの影響 | 向いている用途 |
|---|---|---|---|
time.time() |
1970年1月1日からの経過秒(現在時刻) | 受ける(進んだり戻ったりすることがある) | 「いま何時か」を記録する |
time.monotonic() |
ある時点からの経過秒。戻らないことが保証されている | 受けない | 間隔の計算、タイムアウト、定期実行 |
time.perf_counter() |
短い時間の計測用の高分解能な時計 | 受けない | 処理時間の計測 |
time.time() はPCの時刻合わせで値が飛ぶことがあり、そのタイミングで待ち時間の計算が大きく狂う可能性があります。間隔の計算には、戻らないことが保証されている time.monotonic() を使います。 今回の環境では、monotonicとperf_counterはどちらも clock_gettime(CLOCK_MONOTONIC)(分解能1ナノ秒)で実装されていました(time.get_clock_info() で確認)。
完成形:処理が長引いた回を飛ばす関数
処理が間隔より長くかかったとき、基本形のままだと遅れを取り戻そうとして待ち時間0で連続実行されます。遅れた回は飛ばして、次の区切りから再開する形にしたのが次の関数です。そのままコピーして使えます。
import time
def run_every(interval, func, count=None):
"""func を interval 秒ごとに呼ぶ。処理が間隔を超えたら遅れた回を飛ばす。
count を省略すると止めるまで繰り返す。"""
next_time = time.monotonic()
n = 0
while count is None or n < count:
func()
n += 1
next_time += interval
delay = next_time - time.monotonic()
if delay > 0:
time.sleep(delay)
else:
skipped = int(-delay // interval) + 1
next_time += skipped * interval
print(f" (処理が長引いたため {skipped} 回分を飛ばします)")
time.sleep(max(0.0, next_time - time.monotonic()))
# 動作確認:0.2秒ごとに実行し、3回目だけ0.45秒かかる
start = time.perf_counter()
calls = 0
def job():
global calls
calls += 1
print(f"{calls}回目 {time.perf_counter() - start:5.2f}秒")
if calls == 3:
time.sleep(0.45) # 3回目だけ長引く
run_every(0.2, job, count=6)
実行結果:
1回目 0.00秒
2回目 0.20秒
3回目 0.40秒
(処理が長引いたため 2 回分を飛ばします)
4回目 1.00秒
5回目 1.20秒
6回目 1.40秒
3回目が0.85秒まで長引いたため、0.6秒と0.8秒の回を飛ばし、次の区切りである1.00秒から0.2秒刻みに戻っています。 開始からの刻み(0.0、0.2、0.4…)が最後まで保たれるのがこの書き方の利点です。
流れを図で確認
flowchart TD
A[next_time = 現在の monotonic] --> B[処理を実行]
B --> C[next_time に間隔を足す]
C --> D{next_time まで<br>時間が残っている?}
D -- はい --> E[残り時間だけ sleep]
D -- いいえ --> F[遅れた回数分 next_time を進める]
F --> E
E --> B
Ctrl+Cで待機を中断する
sleep中でもCtrl+Cはすぐ効く
time.sleepで待っている最中にCtrl+Cを押すと、待機はその場で打ち切られ、KeyboardInterrupt が発生します。 何も対処していないと、次のようなトレースバックが表示されて終了します(60秒のsleep中に割り込みシグナルを送って確認しました)。
Traceback (most recent call last):
File "ctrlc2.py", line 2, in <module>
time.sleep(60)
KeyboardInterrupt
try / except で受け止めて、きれいに終える
import time
try:
print("待機を始めます(Ctrl+C で中断)")
t = time.perf_counter()
time.sleep(60)
except KeyboardInterrupt:
print(f"\n{time.perf_counter() - t:.2f}秒で中断されました。後片付けをして終了します")
実行結果(開始から約1.5秒後に割り込みを送った場合):
待機を始めます(Ctrl+C で中断)
1.49秒で中断されました。後片付けをして終了します
60秒の待機が1.49秒で打ち切られ、exceptの処理に移りました。 ファイルを閉じる、途中結果を保存する、といった後片付けはこのexceptか、finally: に書きます。
定期実行ループを止める形
先ほどの run_every と組み合わせると、止めるまで動き続ける定期実行になります。
try:
run_every(1.0, job) # count を省略すると繰り返し続ける
except KeyboardInterrupt:
print("\n停止しました")
別スレッドの待機を止めるなら threading.Event.wait
Ctrl+Cが届くのはメインスレッドです。別スレッドで time.sleep(60) をしていると、そのスレッドは60秒経つまで起きません。外から止められる待機にしたいときは、threading.Event の wait() を使います。
import threading
import time
stop = threading.Event()
def worker():
n = 0
while not stop.wait(0.5): # 0.5秒待つ。set() されたら True が返ってループを抜ける
n += 1
print(" worker tick", n)
print(" worker 停止")
t = threading.Thread(target=worker)
t.start()
time.sleep(1.2) # メイン側の処理の代わり
t0 = time.perf_counter()
stop.set() # 待機中のスレッドを起こす
t.join()
print(f"停止までの時間 {time.perf_counter() - t0:.4f}秒")
実行結果:
worker tick 1
worker tick 2
worker 停止
停止までの時間 0.0001秒
stop.set() を呼んでから0.0001秒でスレッドが止まりました。 0.5秒の待機の途中でも、合図があればすぐに起きます。
| 比べる点 | time.sleep(秒) |
event.wait(秒) |
|---|---|---|
| 時間が来るまで待つ | できる | できる |
| ほかのスレッドから途中で起こす | できない | event.set() で起こせる |
| 戻り値 | None | set() されたら True、時間切れなら False |
| 向いている場面 | 単純な待機 | 停止の合図を受け取りたいループ |
時間切れのときの戻り値は、threading.Event().wait(0.1) を実行して False になることを確認しました。while not stop.wait(間隔): という形は、「間隔を空けて繰り返す」と「止める合図を受け取る」を1行で書けるので、覚えておくと便利です。
asyncio.sleep・Timer・scheduleとの使い分け
使い分け表
| 方法 | 待っている間、ほかの処理は | 途中で止める方法 | 標準ライブラリ | 向いている場面 |
|---|---|---|---|---|
time.sleep(秒) |
同じスレッドの処理は止まる | Ctrl+C(メインスレッド) | はい | スクリプトでの単純な待機、リトライ待ち |
threading.Event().wait(秒) |
同じスレッドの処理は止まる | set() |
はい | 別スレッドの定期処理を止めたい |
await asyncio.sleep(秒) |
同じイベントループのほかのタスクが進む | タスクの cancel() |
はい | async / await で書いた処理の中 |
threading.Timer(秒, func) |
メインの処理はそのまま進む | cancel() |
はい | 数秒後に1回だけ実行したい |
schedule(外部ライブラリ) |
run_pending() を呼ぶループ次第 |
ループを抜ける | いいえ | 「10分ごと」「毎日9時」を読みやすく書きたい |
| cron・タスクスケジューラ | プログラムは都度起動して終わる | OS側の設定で止める | ― | 1日1回など、間隔が長い定期実行 |
async関数の中では asyncio.sleep を使う
async関数の中で time.sleep を呼ぶと、イベントループ全体が止まり、ほかのタスクも進まなくなります。同じ「1秒待つタスクを3つ同時に動かす」処理で比べました。
import asyncio
import time
async def task(name, sec):
await asyncio.sleep(sec) # ほかのタスクに順番を譲って待つ
print(f" {name} 完了 {time.perf_counter() - t0:.2f}秒")
async def main():
await asyncio.gather(task("A", 1), task("B", 1), task("C", 1))
t0 = time.perf_counter()
asyncio.run(main())
print(f"asyncio.sleep 合計 {time.perf_counter() - t0:.2f}秒")
実行結果:
A 完了 1.00秒
B 完了 1.00秒
C 完了 1.00秒
asyncio.sleep 合計 1.01秒
await asyncio.sleep(sec) を time.sleep(sec) に書き換えて同じように実行すると、次の結果になりました。
A 完了 1.00秒
B 完了 2.00秒
C 完了 3.00秒
time.sleep 合計 3.00秒
asyncio.sleepなら3つが同時に待って合計1.01秒、time.sleepだと1つずつ順番に待って合計3.00秒です。 async関数の中での待機は await asyncio.sleep() と覚えておきましょう。
なお、スレッドを分けた場合は time.sleep でも同時に待てます。sleep(1) を行うスレッドを3つ起動して終了を待ったところ、合計は1.00秒でした。time.sleepが止めるのは呼び出したスレッドだけ、という冒頭の説明どおりの結果です。
数秒後に1回だけ実行するなら threading.Timer
import threading
import time
t0 = time.perf_counter()
timer = threading.Timer(0.3, lambda: print(f" Timer 実行 {time.perf_counter() - t0:.2f}秒"))
timer.start()
print("Timer 開始直後")
timer.join()
実行結果:
Timer 開始直後
Timer 実行 0.30秒
start() のあとすぐ次の行に進み、0.3秒後に関数が呼ばれます。 実行前に timer.cancel() を呼ぶと取り消せます(取り消した場合に関数が呼ばれないことも確認しました)。Timerは1回きりの実行用で、繰り返しには前の節の run_every や Event.wait のループが向いています。
「何秒ごと」「毎日何時」を読みやすく書くなら schedule
scheduleは pip install schedule で入る外部ライブラリです(今回は 1.2.2 で確認)。
import time
import schedule
start = time.perf_counter()
def job():
print(f"job 実行 {time.perf_counter() - start:.1f}秒")
schedule.every(1).seconds.do(job)
end = time.monotonic() + 3.5 # 動作確認のため3.5秒で終える
while time.monotonic() < end:
schedule.run_pending() # 実行時刻が来たジョブを動かす
time.sleep(0.1) # 0.1秒ごとに確認する
実行結果:
job 実行 1.0秒
job 実行 2.0秒
job 実行 3.0秒
scheduleは「予定を管理する」ライブラリで、待機そのものは time.sleep() と組み合わせて自分でループを書く形になります。schedule.every(10).minutes.do(job) や schedule.every().day.at("09:00").do(job) のように、予定を文章に近い形で書けるのが特長です(この2つの書き方は今回は未実行です)。
time.sleepを使った実例集
カウントダウンを同じ行に表示する
import time
for i in range(3, 0, -1):
print(f"\r残り {i} 秒", end="", flush=True)
time.sleep(1)
print("\r開始します ")
\r は行の先頭に戻る記号です。end="" で改行を止め、flush=True ですぐ画面に出すことで、同じ行の数字だけが 3 → 2 → 1 と書き換わります。 最後の行は「開始します」のあとに空白を2つ足して、前の文字が残らないようにしています(待機を0.2秒に縮めて、表示が同じ行に上書きされることを確認しました)。
リトライの待ち時間を少しずつ延ばす
通信などが一時的にうまくいかなかったとき、少し待ってからやり直す書き方です。待ち時間を回ごとに2倍にしていく方法は「指数バックオフ」と呼ばれます。
import random
import time
def retry(func, tries=5, base=0.2, cap=2.0):
"""func が ConnectionError を出したら、待ち時間を延ばしながら最大 tries 回試す"""
for attempt in range(1, tries + 1):
try:
return func()
except ConnectionError as e:
if attempt == tries:
raise # 最後の回はそのままエラーを伝える
wait = min(cap, base * 2 ** (attempt - 1)) # 0.2 → 0.4 → 0.8 …(上限 cap)
wait = wait * (0.5 + random.random() / 2) # 50〜100% の範囲でばらつかせる
print(f" {attempt}回目: {e} → {wait:.2f}秒待って再試行")
time.sleep(wait)
# 動作確認用:3回目までは接続エラーになる関数
state = {"n": 0}
def flaky():
state["n"] += 1
if state["n"] < 4:
raise ConnectionError("一時的に接続できません")
return "OK"
random.seed(1)
print(retry(flaky))
実行結果:
1回目: 一時的に接続できません → 0.11秒待って再試行
2回目: 一時的に接続できません → 0.37秒待って再試行
3回目: 一時的に接続できません → 0.71秒待って再試行
OK
| 工夫 | ねらい |
|---|---|
| 待ち時間を回ごとに2倍にする | 相手が回復するまでの時間を確保する |
| 上限(cap)を決める | 待ち時間が延びすぎないようにする |
| 乱数でばらつかせる | 複数のプログラムが同じタイミングで一斉にやり直すのを避ける |
| 最後の回は raise する | うまくいかなかったことを呼び出し元に伝える |
連続アクセスの間隔を空ける
Webサイトや外部のAPIに続けてアクセスするときは、1回ごとに待機を入れて相手のサーバーに負荷をかけないようにします。
import time
urls = ["page1", "page2", "page3"] # 取得したいページの一覧(例)
for url in urls:
print(f"{url} を取得") # ここに取得処理を書く
time.sleep(1) # 次のアクセスまで1秒空ける
空ける時間は、相手のサービスの利用規約やAPIの説明に書かれた回数制限に合わせて決めます。 「1秒に1回まで」のような決まりがある場合は、前の節の run_every を使うと、処理時間を含めて1秒間隔に揃えられます。
条件が整うまで待つ(タイムアウトつき)
「ファイルができるまで待つ」のように、条件を定期的に確認しながら待つ形です。上限時間を決めておくと、待ち続けることがなくなります。
import os
import time
def wait_for_file(path, timeout=10.0, interval=0.2):
"""path ができるまで待つ。timeout 秒以内にできたら True、できなければ False"""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if os.path.exists(path):
return True
time.sleep(interval)
return False
ここでも期限の計算には time.monotonic() を使っています(この関数は今回は未実行です。考え方は一定間隔ループと同じです)。
つまずきやすい点と直し方
| 起きること | 原因 | 直し方 |
|---|---|---|
NameError: name 'sleep' is not defined |
import time なのに sleep(1) と呼んでいる |
time.sleep(1) と書く、または from time import sleep |
ValueError: sleep length must be non-negative |
計算した待ち時間がマイナス | time.sleep(max(0.0, 待ち時間)) |
TypeError: 'str' object cannot be interpreted as an integer |
文字列を渡している | time.sleep(float(値)) |
ミリ秒のつもりで time.sleep(500) と書き、500秒待ってしまう |
単位は秒 | time.sleep(0.5) または time.sleep(500 / 1000) |
| 定期実行の間隔が少しずつ延びる | 処理時間とsleepの超過分が毎回足される | 次の実行時刻を time.monotonic() で持つ |
| 画面(GUI)が待機中に固まる | 画面を動かすスレッドを sleep で止めている | GUIライブラリのタイマー機能(tkinterなら after())を使う |
| async関数の中でほかのタスクが進まない | time.sleep がイベントループを止めている | await asyncio.sleep(秒) に変える |
| 別スレッドがなかなか止まらない | 長い time.sleep の途中では起こせない | threading.Event().wait(秒) に変える |
| カウントダウンが最後にまとめて表示される | 出力がバッファにたまっている | print(..., flush=True) |
よくある質問
Q. time.sleepでミリ秒を指定するにはどう書きますか?
A. 秒に直して小数で渡します。100ミリ秒なら time.sleep(0.1)、1ミリ秒なら time.sleep(0.001) です。time.sleep(ms / 1000) と書くと換算の意図が伝わります。今回のLinux環境では、1ミリ秒指定の平均は約1.16ミリ秒でした。
Q. time.sleepはどのくらい正確ですか? A. 指定した時間より短く終わることはなく、少し長くなります。今回のLinux(Python 3.11.15)での計測では、超過分は平均でおよそ0.06〜0.23ミリ秒でした。マシンが混んでいるとさらに延びる回があります。WindowsとmacOSの値は未実行のため、記事中の計測コードで確かめてください。
Q. sleep中にCtrl+Cを押すとどうなりますか?
A. 待機がその場で打ち切られ、KeyboardInterrupt が発生します。try: ... except KeyboardInterrupt: で囲むと、後片付けをしてから終了できます。今回の確認では、60秒の待機が割り込みから即座に終わりました。
Q. time.sleepとasyncio.sleepはどう使い分けますか?
A. 通常の関数では time.sleep、async def で定義した関数の中では await asyncio.sleep() を使います。3つのタスクで比べると、asyncio.sleepは合計1.01秒、async関数内のtime.sleepは合計3.00秒でした。
まとめ
- time.sleepの引数は秒。 ミリ秒は
0.001、分は60を掛けて換算する - 待機時間は指定より少し長くなる。 Linuxでの実測では超過分は約0.06〜0.23ミリ秒で、1ミリ秒指定は平均1.16ミリ秒
- Python 3.11以降は仕組みが変わった。 Unix系は clock_nanosleep / nanosleep、Windowsは高分解能タイマーを使う(公式ドキュメントで確認)
- whileとsleepだけの定期実行はずれが溜まる。 0.1秒間隔のつもりが10回で約0.27秒遅れた
- 一定間隔にするには、次の実行時刻を time.monotonic() で持ち、残り時間だけ待つ
- Ctrl+Cは KeyboardInterrupt で受け止め、別スレッドの待機は Event.wait で止められるようにする
- async関数の中では await asyncio.sleep()、1回だけの遅延実行は Timer、予定の管理は schedule と使い分ける
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python time.sleepの使い方|秒・ミリ秒の待機と一定間隔実行」
株式会社RJCでは、Pythonの待機処理や定期実行の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。