書いたプログラムが一瞬で走りきってしまい、相手のサーバーへ連続でアクセスしてしまった。あるいは、動きが遅いのは分かるのに、どこが遅いのかまでは分からない。
そんな場面に出会ったことはありませんか。
どちらも、時間を扱うほんの少しの知識で解決します。使うのは、Pythonに最初から入っているtimeモジュールです。
インストールは要りません。import time と書くだけで、待つことも測ることもできるようになります。
この記事では、timeモジュールの中でも出番の多い機能を、実際のコードと一緒に順番に整理していきます。
timeモジュールは、時間そのものを扱う道具箱¶
まずは立ち位置から確認しましょう。timeはPythonに標準で付いてくるライブラリで、追加の準備なしにすぐ使えます。
できることは大きく2つです。処理を待たせることと、時間の長さを測ること。
どちらもOSが持っている機能を薄く包んだつくりになっています。だから動作が軽く、どの環境でもだいたい同じようにふるまいます。
datetimeとtimeは、目的がまったく違う¶
ここは初心者がよく混乱するところです。日付を扱うdatetimeというモジュールも別にあるので、どちらを使えばいいのか迷ってしまいます。
区別はかんたんです。人間に見せる日付や日時ならdatetime、機械として時間を数えるならtime、と考えてください。
具体的な場面で並べると、境目がはっきりします。
| やりたいこと | 使うもの |
|---|---|
| 3日後の日付を求める | datetime |
| 2026年8月25日という形で表示する | datetime |
| 処理を5秒だけ止める | time |
| 処理にかかった秒数を測る | time |
日付の計算やタイムゾーンの扱いを詳しく知りたい方は、こちらでまとめています。【関連記事】Pythonのdatetimeを完全攻略!日付の計算やタイムゾーンで迷わないために
time.sleep()で、処理をいったん止める¶
timeモジュールでいちばん出番が多いのがこれです。指定した秒数だけ、プログラムの進行を止められます。
さっそく手元で動かしてみましょう。
import time
print("開始")
time.sleep(3) # 3秒止まる
print("3秒たちました")
time.sleep(0.5) # 小数も渡せる
print("さらに0.5秒たちました")
渡す秒数は小数でも構いません。0.5と書けば0.5秒、0.1と書けば0.1秒だけ待ちます。
止まるのは、その処理を動かしているスレッドだけです。むずかしく感じるかもしれませんが、ふつうのスクリプトならプログラム全体が止まると考えて差し支えありません。
指定した秒数ぴったりでは戻ってこない¶
知っておくと得をする性質があります。time.sleep(1) と書いても、きっかり1秒後に再開するとは限らないのです。
公式ドキュメントにも、システム内のほかの処理の都合で、要求した時間より長くなることがあると書かれています。sleepが約束してくれるのは最低の待ち時間であって、上限ではありません。
精度そのものは年々よくなっています。Python 3.11では、Windowsで分解能の高いタイマーが使われるようになり、Unixでもclock_nanosleep()やnanosleep()が使えるときはそちらを使うよう変わりました。
とはいえ、ミリ秒単位の正確さが求められる用途にsleepを持ち出すのは避けたほうが無難です。数秒の待機や、次に紹介するアクセス間隔の調整になら、精度は十分すぎるほどあります。
待ち時間が必要になる、現実の場面¶
わざわざ待つ処理なんて要るのか、と思うかもしれません。ところが実務では、驚くほどよく使います。
いちばん多いのは、Webからデータを取ってくる場面です。連続してアクセスすると相手のサーバーに負荷をかけるので、1件ごとに少しだけ間を空けます。
import time
import requests
urls = ["https://example.com/1", "https://example.com/2"]
for url in urls:
response = requests.get(url, timeout=10)
print(url, response.status_code)
time.sleep(1) # 次のアクセスまで1秒空ける
この1行は、相手のサーバーを守るためのマナーでもあります。 自分のプログラムが原因で相手に迷惑をかけると、アクセスそのものを断られてしまうことさえあります。
HTTP通信の基本から確認しておきたい方は、こちらもあわせてどうぞ。【関連記事】Pythonのrequestsとは?WebからデータをとってくるHTTP通信の基本を初心者向けに解説
失敗したときは、待つ時間を伸ばしていく¶
通信が失敗したときの再挑戦にも、sleepが登場します。ただし、毎回同じ秒数で待つのはあまり良い手ではありません。
相手が混み合っているときに一定の間隔で叩き続けると、その混雑をさらに悪化させてしまうからです。失敗するたびに待ち時間を倍にしていくほうが、相手にも自分にも優しいやり方になります。
import time
for attempt in range(5):
try:
result = call_api() # 通信する処理(例)
break
except TimeoutError:
wait = 2 ** attempt # 1秒、2秒、4秒、8秒…と伸びていく
print(f"{wait}秒待ってから再挑戦します")
time.sleep(wait)
この待ち方は指数バックオフと呼ばれます。私は10年ほどエンジニアとして開発に関わってきましたが、固定間隔のリトライが障害の復旧をかえって長引かせた現場を何度も見てきました。
処理時間を測るなら、perf_counter()を使う¶
ここからは測るほうの話です。遅い処理を速くしたいとき、まず必要になるのは、どこにどれだけ時間がかかっているかという数字です。
検索するとtime.time()で測る例が多く出てきます。ですが、経過時間の測定に向いているのは別の関数です。
import time
start = time.perf_counter()
total = sum(range(10_000_000))
elapsed = time.perf_counter() - start
print(f"かかった時間: {elapsed:.3f}秒")
time.perf_counter() は、短い時間を測るために用意された、いちばん分解能の高い時計です。返ってくる値そのものに意味はなく、2回呼んだときの差だけを使います。
では、なぜtime.time()ではないのでしょうか。理由は、time.time()が指しているのが現在時刻だからです。
現在時刻は、ほかのコンピュータと時計を合わせるなどの理由で、途中で修正されることがあります。運悪くその瞬間をまたいでしまうと、測定結果がずれたり、ときにはマイナスになったりします。
時刻を記録したいときはtime.time()、時間の長さを測りたいときはtime.perf_counter()。 この一言を覚えておけば、もう迷いません。
4つの時計を、目的で選び分ける¶
timeモジュールには時計がいくつもあり、名前を眺めただけでは違いが分かりません。ここで整理しておきましょう。
| 関数 | 何を返すか | 向いている用途 | sleep中の時間 |
|---|---|---|---|
time.time() |
エポックからの経過秒数 | 時刻の記録、ファイル名 | 含む |
time.monotonic() |
巻き戻らない時刻 | タイムアウト、間隔の判定 | 含む |
time.perf_counter() |
最も分解能の高い時刻 | 処理時間の計測 | 含む |
time.process_time() |
CPUが働いた時間 | 計算の重さの調査 | 含まない |
エポックというのは時間を数える基準点のことで、WindowsやUnix系の多くの環境では1970年1月1日0時(UTC)を指します。time.time()は、そこからの経過秒数を小数で返しているわけです。
time.monotonic()は、名前のとおり巻き戻らないことが保証された時計です。時刻合わせの影響を受けないので、タイムアウトの判定のように、途中で時間が逆戻りしたら困る場面で使われます。
最後のprocess_time()だけ、右端の列が違うことに気づいたでしょうか。これはCPUが実際に働いた時間なので、通信の待ち時間やsleepの時間は数えません。
つまり、全体では10秒かかったのにprocess_time()では0.2秒しか進んでいなければ、その処理は計算ではなく待ちで時間を使っていると分かります。原因の切り分けに、とても役立つ性質です。
小数の誤差が気になる場合は、末尾に_nsが付いたtime.perf_counter_ns()のような関数も使えます。こちらは整数のナノ秒で結果を返してくれます。
小さなコードの速さを比べるなら、timeit¶
自分で測る方法を覚えたところで、注意点をひとつ。1回だけ測った数字は、ほとんど参考になりません。
パソコンは同時にいろいろな処理を抱えているので、まったく同じコードでも測るたびに結果が揺れます。一瞬で終わる処理ほど、その揺れの影響を大きく受けてしまいます。
そこで使うのが、標準ライブラリのtimeitです。同じコードを何度も繰り返し実行して、ばらつきをならしてくれます。
python -m timeit -n 1000 "'-'.join(str(n) for n in range(100))"
コマンドラインからそのまま呼べるのが便利なところです。-nは1セットあたりの繰り返し回数、-rは測定を何セット行うかの指定になります。
もちろんPythonのコードの中からも使えます。ただしtimeit.timeit()は回数を指定しないと100万回も実行するので、重い処理を測るときはnumberを必ず小さくしてください。
そもそも速さは、書き方そのものによって桁が変わることもあります。処理の重さを見積もる考え方は、こちらで解説しています。【関連記事】「実行時間が終わらない…」を卒業する!あなたのコードを100倍速くする計算量の考え方
現在時刻を、人が読める形にする¶
timeモジュールにも、時刻を文字列へ整える機能があります。ログのファイル名を作るときなどに手軽です。
数値のままでは読めないので、いったん地方時に直してから書式を当てます。
import time
now = time.time()
print(now) # 1787000000.123456 のような数値
local = time.localtime(now)
print(time.strftime("%Y-%m-%d %H:%M:%S", local))
print(time.strftime("log_%Y%m%d.txt", local))
localtime()でその環境の地方時に直し、strftime()で好きな書式へ整えます。%Yが4桁の年、%mが月、%dが日です。
ただし、日付の計算や表示が目的の中心になるなら、素直にdatetimeを使うほうが読みやすくなります。timeのこの機能は、ちょっとした記録に添えるくらいがちょうどよい距離感です。
初心者がつまずきやすいところ¶
最後に、実際によく見かける失敗をまとめておきます。当てはまるものがないか、確認してみてください。
| 症状 | 原因 | 対処 |
|---|---|---|
| sleepでNameErrorが出る | import time を書いていない |
ファイルの先頭で読み込む |
| sleepが急に使えなくなる | timeという名前の変数を作った | 変数名を別のものに変える |
| 測った時間がマイナスになる | time.time()で測っている |
perf_counter()に変える |
| 待っている間に画面が固まる | GUIアプリの本体でsleepした | フレームワークのタイマー機能を使う |
| 何度測っても同じ数字が出る | 表示を丸めすぎている | :.3fのように桁数を増やす |
とくに多いのが2つめです。time = 0のような行を書くと、モジュールのほうが変数に隠されてしまいます。
私自身、動かないと相談を受けたコードを開いたら、ずっと上のほうにその一行が書かれていた、という経験があります。エラーメッセージを読んでも気づきにくい種類の事故です。
まとめと、次の一歩¶
覚えることは、そう多くありません。待つならsleep、測るならperf_counter。まずはこの2つで十分に戦えます。
sleepは最低の待ち時間を保証するもので、ぴったりでは戻ってこない。この性質さえ頭に置いておけば、精度で悩むこともなくなります。
一方で、毎朝決まった時刻に処理を動かしたいときは、sleepでずっと待たせるのではなく、OSの仕組みに任せるほうが確実です。【関連記事】Pythonで定期実行するには?cronとタスクスケジューラの基本
まずは手元のスクリプトを開いて、いちばん遅いと感じる処理をperf_counter()で挟んでみてください。数字が見えた瞬間から、改善は感覚の話ではなく具体的な作業に変わります。
ここまでお読みいただきありがとうございました。