前提
本日のお題
8. ポモドーロタイマー(CLI)
何を作る?
25分作業 + 5分休憩を繰り返すタイマー。残り時間をコンソールに表示する。
学べること
-
time.sleepとループ制御 - シンプルなステートマシン(作業 / 休憩)
- Ctrl+C などの割り込み処理(
KeyboardInterrupt)
面白いところ
- 実用的で、作った直後から自分の作業で使える
- 終わったときに音を鳴らす / OS 通知するなど拡張余地が大きい
回答
Day8にしてChatGPTに作ってもらったコードを読む方が知らないことが出てきて勉強になる気がしているので今回も作ってもらいます。
コード
08_pomodoro.py
"""
ポモドーロタイマー(CLI 版)
デフォルト:
- 作業 25 分
- 休憩 5 分
- 4 サイクル
使い方:
python pomodoro.py
python pomodoro.py --work 50 --rest 10 --cycles 2
"""
import argparse
import time
import sys
def format_time(seconds: int) -> str:
"""秒数を MM:SS 形式の文字列に変換する。"""
m, s = divmod(seconds, 60)
return f"{m:02d}:{s:02d}"
def run_interval(label: str, seconds: int) -> None:
"""
1つのインターバル(作業 or 休憩)を実行する。
残り時間を 1 秒ごとに上書き表示する。
"""
print(f"\n--- {label} 開始 ({format_time(seconds)}) ---")
try:
for remaining in range(seconds, -1, -1):
time_str = format_time(remaining)
# \r で行頭に戻って同じ行を上書き
print(f"\r[{label}] 残り {time_str}", end="", flush=True)
if remaining > 0:
time.sleep(1)
# 行末に改行を入れる
print()
except KeyboardInterrupt:
# Ctrl+C が押されたときにきれいに抜ける
print("\n[INFO] タイマーが中断されました。")
raise
def beep() -> None:
"""簡易的なビープ音(ターミナル側が対応していれば鳴る)。"""
# \a は BEL (Bell) 文字
print("\a", end="", flush=True)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="シンプルなポモドーロタイマー(CLI)"
)
parser.add_argument(
"--work",
type=int,
default=25,
help="1 回あたりの作業時間(分) デフォルト: 25",
)
parser.add_argument(
"--rest",
type=int,
default=5,
help="1 回あたりの休憩時間(分) デフォルト: 5",
)
parser.add_argument(
"--cycles",
type=int,
default=4,
help="作業+休憩のサイクル回数 デフォルト: 4",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
work_sec = max(args.work, 1) * 60
rest_sec = max(args.rest, 1) * 60
cycles = max(args.cycles, 1)
print("=== ポモドーロタイマー ===")
print(f"作業: {args.work} 分 / 休憩: {args.rest} 分 / サイクル: {cycles} 回")
print("Ctrl+C でいつでも中断できます。")
try:
for i in range(1, cycles + 1):
print(f"\n=== サイクル {i}/{cycles} ===")
run_interval("作業", work_sec)
beep()
# 最後のサイクルの後は休憩を挟まない
if i < cycles:
run_interval("休憩", rest_sec)
beep()
except KeyboardInterrupt:
# run_interval 内で処理済みメッセージは出しているので、
# ここでは終了メッセージだけ
print("[INFO] ポモドーロタイマーを終了します。")
sys.exit(0)
print("\n=== 全サイクル完了!おつかれさまでした! ===")
# 完了時に少しだけビープを多めに鳴らす
for _ in range(3):
beep()
time.sleep(0.3)
if __name__ == "__main__":
main()
実行例
※25分+5分は長いので25秒+5秒で動作確認しました。
$python 08_pomodoro.py
=== ポモドーロタイマー ===
作業: 25 分 / 休憩: 5 分 / サイクル: 4 回
Ctrl+C でいつでも中断できます。
=== サイクル 1/4 ===
--- 作業 開始 (00:25) ---
[作業] 残り 00:00
--- 休憩 開始 (00:05) ---
[休憩] 残り 00:00
=== サイクル 2/4 ===
--- 作業 開始 (00:25) ---
[作業] 残り 00:00
--- 休憩 開始 (00:05) ---
[休憩] 残り 00:00
=== サイクル 3/4 ===
--- 作業 開始 (00:25) ---
[作業] 残り 00:00
--- 休憩 開始 (00:05) ---
[休憩] 残り 00:00
=== サイクル 4/4 ===
--- 作業 開始 (00:25) ---
[作業] 残り 00:00
=== 全サイクル完了!おつかれさまでした! ===
感想
-
print("XXX", end="", flush=True)とすると現在の行で表示を更新できる、便利 -
\aでターミナルでも音がならせるのは初めて知った - time.sleepって正確に時間計れてない気がするけどまぁいいか…。
- divmodっていつから生えた?もとからいた?(知らなかったの恥ずかしい説)
トマトタイマーでも勉強になるものですね。