学习25分钟、休息5分钟——把第二十一篇的倒计时升级成完整的工作法工具
一、为什么你学不进去,可能不是意志力的问题
你有没有过这种经历:坐下来学习,刚看了两页书,手机亮了;回完消息想继续,脑子里开始想晚饭吃什么;等回过神,半小时没了,书还在第2页。
结论通常是”我意志力太差”。但有个意大利人不同意。
1980年代,Francesco Cirillo发明了一种方法:不要求你学一整天,只要求你专注25分钟。25分钟后休息5分钟,算一个”番茄”。攒够4个番茄,来一次长休息。
就这么简单,但它风靡全世界。原理很反直觉:把”学习一晚上”这种吓人的目标,拆成”熬过25分钟”这种做得到的小任务,大脑就不抗拒了。而且”番茄计数”会让你有种攒金币的快感。
今天我们用程序实现它。这一篇会用到:time模块(第二十一篇刚学的)、datetime(记录日期时间)、循环(工作→休息→工作的循环套循环)、条件判断(长短休息的切换)、文件操作(记录完成统计)、格式化输出(进度显示)。
做完这个,你就有了一个能天天用的专注力工具,还有它生成的每日报告。
二、番茄钟的工作流程
开始
│
├─ 专注25分钟 🔥
│ └─ 完成 → 番茄数+1
├─ 休息5分钟 ☕
│
├─ 番茄数 < 4?→ 回去继续专注
└─ 番茄数 = 4 → 长休息15分钟 → 番茄数清零,重新开始
注意这个结构:外层是”一轮一轮番茄”的循环,内层是”每轮里专注+休息”的流程。循环套循环——第六篇学的东西,今天要正经用一次。
三、先跟AI聊
你的提示词:
用Python写一个番茄钟计时器(命令行版)。规则:专注25分钟,完成后休息5分钟;每完成4个番茄钟,休息15分钟;长休息后番茄计数清零。用time.sleep每秒递减显示剩余时间(格式mm:ss),专注和休息阶段用不同文字提示。用循环和条件判断实现,中文界面,详细注释。
四、基础版代码
import time
# ========== 番茄钟设置 ==========
专注时长 = 25 * 60 # 25分钟,换算成秒
短休息 = 5 * 60 # 5分钟
长休息 = 15 * 60 # 15分钟
每轮番茄数 = 4 # 攒够4个来长休息
def 倒计时(秒数, 标签):
"""显示一个带标签的倒计时,数完为止"""
剩余 = 秒数
while 剩余 > 0:
print(f"r{标签} {剩余 // 60:02d}:{剩余 % 60:02d}", end="")
time.sleep(1)
剩余 -= 1
print() # 换行,免得接上下一行
def 主循环():
"""番茄钟的主流程"""
print("=== 🍅 番茄钟 ===")
print("规则:专注25分钟 → 休息5分钟,攒够4个长休息15分钟")
print("按 Ctrl+C 可以随时退出n")
番茄计数 = 0 # 本轮完成了几个番茄
while True: # 番茄一轮一轮永远转下去
# ---- 阶段1:专注 ----
input("准备好开始专注了吗?按回车开始……")
倒计时(专注时长, "🔥 专注中")
番茄计数 += 1
print(f"🎉 专注完成!这是第{番茄计数}个番茄!n")
# ---- 阶段2:休息 ----
if 番茄计数 % 每轮番茄数 == 0:
# 攒够4个:长休息,然后清零
print("🏆 一轮4个番茄全部完成!")
倒计时(长休息, "🌴 长休息")
番茄计数 = 0
else:
倒计时(短休息, "☕ 短休息")
print("休息结束,回来继续!n")
主循环()
五、逐行理解
5.1 常量区:设置放在一起
专注时长 = 25 * 60
短休息 = 5 * 60
长休息 = 15 * 60
每轮番茄数 = 4
时长设置全部放在程序开头,用有意义的名字命名。想改成”专注50分钟”?改第一个数字就行,不用翻代码找魔法数字。把”可配置的东西”和”逻辑”分开,是写代码的好习惯。
5.2 倒计时函数:第二十一篇的老朋友
def 倒计时(秒数, 标签):
剩余 = 秒数
while 剩余 > 0:
print(f"r{标签} {剩余 // 60:02d}:{剩余 % 60:02d}", end="")
time.sleep(1)
剩余 -= 1
print()
和上一篇的倒计时一模一样,只是加了个标签参数——专注时显示”🔥 专注中”,休息时显示”☕ 短休息”。同一个函数,传不同参数,干不同的活。第七篇说的函数复用,这就是实例。
最后那个光秃秃的print()是换行:倒计时用了r原地刷新,数完后光标还停在那行上,补个换行让后面的输出从新行开始。小细节,但没有它输出会挤成一团。
5.3 取模判断:什么时候长休息
if 番茄计数 % 每轮番茄数 == 0:
%是取余数(第二十一篇学过)。番茄计数是4的倍数时(4、8、12……),除以4余数为0,该长休息了。
用取模判断”每N次做一次特殊的事”,是循环里的经典技巧:每逢3的倍数打印Fizz、每100条数据存一次盘、每4个番茄长休息……全是这一个套路。
5.4 双层循环
while True: # 外层:番茄无限循环
倒计时(专注时长, ...) # 内层:倒计时自己的循环
...
倒计时(短休息, ...) # 又一个内层循环
外层循环转一圈 = 一个完整的”专注+休息”。内层的倒计时循环则负责把这25分钟一秒一秒地走完。时间在两层循环里被”切”成了规整的小块——这正是番茄钟的精髓。
5.5 input当”开始按钮”
input("准备好开始专注了吗?按回车开始……")
这招很妙:用input暂停程序,等用户按回车才开始。它既是”确认”(人回来了吗),又是”仪式感”(按下按钮,进入状态)。没有它,程序一启动就倒计时,你去倒杯水回来已经过去3分钟了。
六、运行试试
python 番茄钟.py
=== 🍅 番茄钟 ===
准备好开始专注了吗?按回车开始……
🔥 专注中 24:59
然后把窗口放在屏幕角落,开始干活。25分钟后:
🎉 专注完成!这是第1个番茄!
☕ 短休息 04:59
用一天你会发现三个可以改进的地方:
- 每次专注都是25分钟,但我有时候只想专注15分钟啊
- 今天到底完成了几个番茄?程序没记住
- 想看看这一周我每天专注了多少
七、第一个改进:自定义时长
让玩家自己选专注时长。顺便学一个新写法——用字典当菜单:
# 预设方案:字典做菜单
时长方案 = {
"1": ("标准番茄", 25, 5),
"2": ("短冲刺", 15, 3),
"3": ("深度工作", 50, 10),
"4": ("自定义", None, None) # None表示要手动输入
}
def 选择时长():
"""让用户选择专注时长,返回(专注分钟, 休息分钟)"""
print("n选择专注方案:")
for 编号, (名称, 专注分, 休息分) in 时长方案.items():
if 专注分:
print(f" {编号}. {名称}(专注{专注分}分钟,休息{休息分}分钟)")
else:
print(f" {编号}. {名称}")
选择 = input("请选择:").strip()
if 选择 not in 时长方案:
print("无效选择,用标准番茄")
return 25, 5
名称, 专注分, 休息分 = 时长方案[选择]
# 自定义:让用户自己输
if 专注分 is None:
try:
专注分 = int(input("专注多少分钟(5-120):"))
休息分 = int(input("休息多少分钟(1-30):"))
except ValueError:
print("输入不对,用标准时长")
return 25, 5
# 范围检查
if not (5 <= 专注分 <= 120 and 1 <= 休息分 <= 30):
print("超出范围,用标准时长")
return 25, 5
print(f"✅ {名称}:专注{专注分}分钟,休息{休息分}分钟")
return 专注分, 休息分
主循环开头调用它:
专注分, 休息分 = 选择时长()
专注时长 = 专注分 * 60
短休息 = 休息分 * 60
几个亮点:
- 字典当菜单:编号→(名称, 专注, 休息)。比一长串if-elif清爽,加方案只需加一行数据
is None判断:自定义方案的值是None,看到None就走手动输入分支。None是第四篇学过的”空值”,专门用来标记”这里还没东西”- 多层防御:选错菜单→默认标准;输非数字→默认标准;超范围→默认标准。每一步都有兜底,程序永远能给出一个可用的时长,绝不卡死
try...except包两个输入:一句except接住两个input的错,谁出错都走兜底
八、第二个改进:完成统计 — 程序记住你的战绩
现在程序重启就忘了今天攒了几个番茄。用第九篇的文件操作解决——每次完成番茄,就往文件里记一笔:
import json
from datetime import datetime
记录文件 = "番茄记录.json"
def 加载记录():
"""读取历史记录,返回 {日期: 番茄数} 的字典"""
try:
with open(记录文件, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return {} # 没有记录文件就从空开始
except json.JSONDecodeError:
print("⚠️ 记录文件损坏,重新开始")
return {}
def 记录一个番茄(记录):
"""完成一个番茄,记录到今天名下"""
今天 = datetime.now().strftime("%Y-%m-%d")
记录[今天] = 记录.get(今天, 0) + 1 # 今天的数量+1
# 立刻存盘,程序崩了记录也不丢
with open(记录文件, "w", encoding="utf-8") as f:
json.dump(记录, f, ensure_ascii=False, indent=2)
return 记录[今天]
在主循环里,专注完成之后调用:
番茄计数 += 1
今日总数 = 记录一个番茄(记录)
print(f"🎉 专注完成!第{番茄计数}个番茄(今天累计{今日总数}个)")
数据结构就是一个字典:{"2025-01-15": 5, "2025-01-16": 3, ...}——日期做键,当天番茄数做值。第二十二篇”按天分组累加”的同款结构,这次连分组都省了:天然按天分组,因为键就是日期。
注意记录[今天] = 记录.get(今天, 0) + 1——又是那个”取不到当0″的老套路。今天的第一个番茄:get返回0,加1存1。第二个:get返回1,加1存2。
每次完成立刻存盘,不等退出。这样程序中途崩溃(或者你直接关窗口),已完成的番茄也都在。重要的数据,尽早落盘——这是真实软件的常规做法。
九、第三个改进:日报表 — 看看这周的自己
有了记录文件,就能做报表了。加一个”查看统计”功能:
def 查看统计():
"""显示最近7天的番茄统计"""
记录 = 加载记录()
if not 记录:
print("📭 还没有记录,先完成一个番茄吧!")
return
print("n📊 最近7天专注报告")
print("=" * 38)
# 生成最近7天的日期列表
from datetime import timedelta
今天 = datetime.now()
总番茄 = 0
总时长 = 0
for 往前几天 in range(6, -1, -1): # 6天前、5天前……今天
日期 = (今天 - timedelta(days=往前几天)).strftime("%Y-%m-%d")
数量 = 记录.get(日期, 0)
总番茄 += 数量
总时长 += 数量 * 25 # 按标准番茄算时长
# 用方块画个迷你柱状图
条 = "🍅" * 数量 if 数量 else "—"
星期名 = ["一", "二", "三", "四", "五", "六", "日"][
(今天 - timedelta(days=往前几天)).weekday()]
print(f" 周{星期名} {日期} {数量}个 {条}")
print("=" * 38)
print(f" 7天总计:{总番茄}个番茄,约{总时长 // 60}小时{总时长 % 60}分钟")
if 总番茄:
print(f" 日均:{总番茄 / 7:.1f}个")
新知识点:timedelta日期运算
from datetime import timedelta
日期 = (今天 - timedelta(days=往前几天)).strftime("%Y-%m-%d")
timedelta(days=3):表示”3天”这个时间段- 日期 – timedelta:日期减去3天,得到3天前的日期
datetime模块能直接做日期加减,不用你自己算”这个月有没有31号”。第七篇说import能省很多事,这就是例子——日期计算这种麻烦事,标准库早就帮你做好了。
新知识点:weekday()
(今天 - timedelta(days=往前几天)).weekday()
weekday()返回星期几的数字:周一=0,周二=1……周日=6。用它当列表下标(星期名[数字]),就把数字变成”一二三四五六日”。数字→下标→文字,这个转换链条第四篇学过(数据类型+列表)。
画个迷你图表
条 = "🍅" * 数量 if 数量 else "—"
字符串乘法(第二十一篇进度条用过):几个番茄就画几个🍅。if 数量 else "—"是三元表达式——”有条件的选择”的紧凑写法。数量是0就画个横杠(0乘任何字符串是空字符串,光秃秃不好看)。
输出效果:
📊 最近7天专注报告
======================================
周一 2025-01-13 3个 🍅🍅🍅
周二 2025-01-14 5个 🍅🍅🍅🍅🍅
周三 2025-01-15 0个 —
周四 2025-01-16 4个 🍅🍅🍅🍅
周五 2025-01-17 2个 🍅🍅
周六 2025-01-18 0个 —
周日 2025-01-19 1个 🍅
======================================
7天总计:15个番茄,约6小时15分钟
日均:2.1个
一周的专注情况一目了然。数据被看见,才会被改变——这就是报告的意义。
十、几个值得注意的设计决定
回头看整个程序,有几个决定值得琢磨:
1. 记录用字典而不是列表。 如果用列表存[{"日期":..., "数量":...}],查”今天有几个”得遍历整个列表。用日期做键的字典,记录.get(今天, 0)一步到位。数据结构跟着查询方式选——总是按键查,就用字典。
2. 完成即存盘。 不是退出时才保存,而是每完成一个番茄立刻写文件。多花几毫秒,换来崩溃不丢数据。
3. 倒计时做成函数,专注和休息共用。 加个标签参数就区分了场景。如果专注和休息各写一遍倒计时,代码直接多一倍。
4. 每层输入都有兜底。 选错菜单、输错数字、超出范围,全部有默认值接住。用户怎么折腾,程序都能给出可用的时长。
十一、回顾一下用到了什么
| 知识点 | 在哪里用的 |
|---|
|——–|———–|
| time.sleep | 每秒倒计时 |
|---|---|
| while循环 | 番茄主循环、倒计时循环 |
| 循环套循环 | 主流程套倒计时 |
| for循环 | 遍历7天、遍历菜单 |
| range(6, -1, -1) | 倒序生成6→0 |
| 条件判断 | 长短休息切换、输入验证 |
| 取模% | 每4个番茄长休息 |
| 函数 | 倒计时、选择时长、记录、报表 |
| 函数参数 | 标签、秒数 |
| return | 返回时长、返回统计 |
| 字典 | 时长方案菜单、按天记录 |
| 字典.get | 累加番茄数、查某天数量 |
| None | 标记自定义方案 |
| is None | 判断走不走自定义分支 |
| 列表+下标 | 数字转星期名 |
| 文件操作 | JSON记录读写 |
| try/except | 多种错误兜底 |
| datetime/timedelta | 日期、日期减N天 |
| weekday() | 星期几 |
| 字符串乘法 | 🍅柱状图 |
| 三元表达式 | 数量为0画横杠 |
| f-string格式化 | mm:ss显示、对齐 |
十二、今日小练习
练习1:暂停功能
专注到一半来电话了怎么办?现在的程序没法暂停。加一个功能:倒计时期间用户按回车可以暂停,再按回车继续。(提示:这是个难题——time.sleep期间程序听不见键盘。可以每秒醒来时用某种方式检查键盘。先问问AI有什么办法,你会学到”非阻塞输入”这个概念。做不出来也没关系,思考过程本身就是收获。)
练习2:导出周报
写一个函数,把本周(周一到周日)的记录导出成一个文本文件”周报.txt”,内容包括每天的番茄数、总计、日均、最好的一天是哪天。提示:"w"模式写文件,max()函数找最大值(配字典的.get用:max(记录, key=记录.get))。
练习3:任务标签
让每个番茄可以贴标签:开始专注前输入”这25分钟做什么”(如:写论文、刷数学题),记录改成{"2025-01-15": {"写论文": 2, "刷数学题": 3}}——字典套字典。统计时能看到”这周写论文花了几个番茄”。想想哪些函数要跟着改?
下一篇(阶段收官):个人档案管理器 — 把这一路学的所有东西装进一个程序:证件信息、账号密码、联系方式,增删改查、搜索统计、加密存储、导出报表。这是阶段2的毕业作品。
发表回复