煮面计时5分钟,开会前10分钟提醒——这次用上全系列唯一的新东西:time模块
一、你缺的不是时间管理,是一个帮你盯时间的人
场景一:煮面条,说好煮5分钟。你去刷手机,一抬头——20分钟过去了,面糊了。
场景二:下午3点开会,你想提前10分钟准备。结果一忙起来抬头看表——3点05了。
场景三:敷面膜15分钟、煮鸡蛋8分钟、烤箱25分钟、午睡20分钟……
生活里到处是”X分钟之后提醒我”的需求。手机上有倒计时App,但今天我们要自己写一个。因为写这个程序会用到一个新东西——time模块。说是新东西,其实只有两个函数,5分钟就能学会。
学会之后,这个提醒器是你自己的:想怎么改就怎么改。多个计时器、声音提醒、显示剩余时间,全按你的习惯来。
二、time模块:就两个函数
Python自带一个叫time的模块(第十一篇说的”工具箱”),跟时间有关的基础功能都在里面。我们只需要两个:
第一个:time.sleep(秒数) —— 让程序睡一会儿
import time
print("我要休息3秒……")
time.sleep(3) # 程序在这里暂停3秒,什么都不干
print("3秒到了!")
time.sleep(3)就是”程序暂停3秒”。这3秒里程序完全不动,时间到了才继续往下走。煮面计时的本质,不就是”暂停5分钟,然后喊一声”吗?
第二个:time.time() —— 问一下现在几点了
现在 = time.time()
print(现在) # 输出类似 1737024000.0
这不是给人看的时间,而是”从1970年1月1日0点到现在过了多少秒”——一个巨大的数字。它不重要也不好看,但可以做减法:
开始 = time.time()
# ... 做一些事情 ...
结束 = time.time()
print(f"这件事花了{结束 - 开始:.1f}秒")
两个时间一减,就知道过了多久。计时器的原理就这么朴素。
三、先跟AI聊
你的提示词:
用Python写一个倒计时提醒器。用户输入分钟数,程序开始倒计时,时间到之后打印”时间到!”提醒。使用time模块的time.sleep函数,循环每秒递减,用print显示剩余时间。中文界面,代码加详细注释。
四、基础版:能用的倒计时
import time
print("=== 倒计时提醒器 ===")
# 获取倒计时时长
try:
分钟 = int(input("倒计时几分钟:"))
except ValueError:
print("请输入数字!")
exit() # 直接退出程序
# 换算成总秒数
总秒数 = 分钟 * 60
print(f"好的,{分钟}分钟后提醒你!n")
# 倒计时主循环
剩余 = 总秒数
while 剩余 > 0:
print(f"r⏰ 剩余 {剩余 // 60:02d}:{剩余 % 60:02d}", end="")
time.sleep(1) # 每次暂停1秒
剩余 -= 1 # 剩余秒数减1
# 循环结束 = 时间到
print("n🔔 时间到!时间到!时间到!")
五、逐行理解
5.1 输入处理
try:
分钟 = int(input("倒计时几分钟:"))
except ValueError:
print("请输入数字!")
exit()
老朋友了:try/except处理非法输入。exit()是新面孔——它的意思是”程序到此结束”。输错了就别往下走了,直接关机走人。
5.2 秒数换算
总秒数 = 分钟 * 60
time模块只认秒,不认分钟。所以5分钟要换算成300秒再交给它。单位要搞对——第二十篇单位转换器刚练过这个。
5.3 倒计时循环
剩余 = 总秒数
while 剩余 > 0:
print(f"r⏰ 剩余 {剩余 // 60:02d}:{剩余 % 60:02d}", end="")
time.sleep(1)
剩余 -= 1
循环每转一圈 = 过了1秒:
- 显示:打印剩余时间
- 睡:
time.sleep(1)暂停1秒 - 减:剩余秒数减1
- 回到第1步,直到
剩余 > 0不成立
这行显示代码信息量很大,拆开看:
r(回车符):光标回到本行开头。下次print会覆盖这次的内容——所以数字是”原地刷新”的,不是一行行往下滚。很多命令行进度条都靠这招。
end="":print默认结尾换行,end=""把换行去掉,这样刷新才在同一行生效。
剩余 // 60:整除(第六篇提过),总秒数里有多少个完整的分钟。300秒 // 60 = 5分钟。
剩余 % 60:取余数,除完分钟剩下的零头秒数。305秒 % 60 = 5秒。
:02d:格式化,数字占2位,不足补0。5显示成05。所以305秒显示成05:05——和电子表一个样式。
//和%这对组合,就是”分钟:秒钟”的转换公式,以后做倒计时、做时钟显示都用得上。
5.4 时间到
print("n🔔 时间到!时间到!时间到!")
循环结束,说明剩余秒数减到0了。前面加个n换行,免得接在倒计时那行屁股后面。
六、运行试试
python 倒计时提醒器.py
倒计时几分钟:1
好的,1分钟后提醒你!
⏰ 剩余 00:01
🔔 时间到!时间到!时间到!
看着屏幕上数字每秒跳一格,还挺有仪式感的。煮面试一次,真的好用。
但用几天你就会觉得差点意思:
- 等待的时候干不了别的?(其实可以,切走就行,程序在后台跑)
- 提醒只有一行字,忙着呢根本注意不到
- 一次只能跑一个,煮面的时候还想烤东西怎么办?
一个一个来。
七、第一个改进:声音提醒 — 把人叫醒
光打印一行字,埋头干活的人根本看不见。得响。
终端里有个历史悠久的骚操作:打印转义字符a,它会触发终端响铃(ASCII码7,叫”BEL”字符):
print("a")
在大多数终端里,这会”哔”一声(有的终端会闪屏)。虽然是上世纪70年代传下来的老古董,但简单有效。升级版提醒:
def 响亮提醒(消息):
"""时间到时的提醒:响铃+醒目文字"""
print("n") # 先空两行,让提醒更显眼
for i in range(5): # 响5声
print("🔔 " + 消息)
print("a", end="", flush=True) # 响铃
time.sleep(0.5) # 两声之间隔半秒
两个细节:
for i in range(5):循环5次。第六篇学的。响一声容易被忽略,连响5声跑不掉。flush=True:强制立刻输出。有些环境下print的内容会先攒着(缓冲),加这个参数保证铃声立即触发,不会攒到最后一起响。
如果你的终端不响(很多现代终端默认静音),还可以用系统命令:
import os
os.system('printf "\a" > /dev/tty') # Linux/Mac
或者干脆调用系统自带的提示音程序。不过这些属于”锦上添花”,a够用了。
八、第二个改进:多个计时器 — 煮面和烤箱同时跑
做饭经常是几个计时同时进行:面条5分钟、烤箱25分钟、汤40分钟。一个个排队等可不行,得记录多个计时器,每秒检查一次谁到点。
这个设计变了:不再是”睡一大觉等一个”,而是每秒醒来一次,把所有计时器检查一遍。
import time
计时器列表 = [] # 每个元素是 {"名称": ..., "剩余秒数": ...}
def 添加计时器():
"""添加一个新的计时器"""
名称 = input("计时器名称(如:煮面):").strip()
if not 名称:
print("❌ 名称不能为空")
return
try:
分钟 = int(input("几分钟:"))
except ValueError:
print("❌ 请输入数字")
return
if 分钟 <= 0:
print("❌ 时间要大于0")
return
计时器列表.append({"名称": 名称, "剩余秒数": 分钟 * 60})
print(f"✅ 已添加:{名称}({分钟}分钟)")
def 显示状态():
"""显示所有计时器的剩余时间"""
if not 计时器列表:
print("📭 没有正在运行的计时器")
return
print("n" + "-" * 36)
for t in 计时器列表:
秒 = t["剩余秒数"]
print(f" ⏰ {t['名称']:<10} 剩余 {秒 // 60:02d}:{秒 % 60:02d}")
print("-" * 36)
def 主循环():
"""每秒检查一次所有计时器"""
print("=== 多功能倒计时提醒器 ===")
print("提示:先添加计时器,然后程序开始统一倒计时n")
while True:
显示状态()
命令 = input("输入名称添加计时器,输入go开始倒计时:").strip()
if 命令 == "go":
if not 计时器列表:
print("至少添加一个计时器!")
continue
break
elif 命令:
# 输入的不是go,当作名称?这里简化:直接调添加
添加计时器()
# ===== 倒计时阶段 =====
while 计时器列表: # 只要还有计时器就继续
time.sleep(1) # 每秒醒一次
到点的 = [] # 记录这秒谁到点了
for t in 计时器列表:
t["剩余秒数"] -= 1
if t["剩余秒数"] <= 0:
到点的.append(t)
# 把到点的提醒一遍,然后从列表里移除
for t in 到点的:
响亮提醒(f"{t['名称']}时间到!")
计时器列表.remove(t) # 从列表删除这个计时器
# 剩下的显示一眼
if 计时器列表:
简报 = " | ".join(f"{t['名称']}{t['剩余秒数'] // 60}:{t['剩余秒数'] % 60:02d}"
for t in 计时器列表)
print(f"r⏳ {简报}", end="")
print("n所有计时器都结束啦!")
主循环()
看着代码变长了,但每一行都是你认识的东西:
计时器列表:列表装字典——第十七篇成绩管理器的同款数据结构。一个计时器=一个字典,两个键:名称、剩余秒数。- 每秒检查一遍所有计时器:
for t in 计时器列表遍历,逐个减1。这就是”循环套循环”:外层while控制时间流逝,内层for处理每个计时器。 - 先记账再删:遍历列表的时候不能直接删除元素——边遍历边删会漏掉元素(Python的经典陷阱)。所以先用
到点的列表把”该删的”记下来,遍历完了再统一删。这个小心思,值得记住。 列表.remove(元素):按内容删除。跟del 列表[下标](按位置删除,第十二篇用过)是一对兄弟。" | ".join(...):把所有计时器的状态拼成一行,煮面0:12 | 烤箱18:03。第二十篇刚用过join。
现在煮面、烤箱、炖汤三个计时器一起跑,谁到点提醒谁,其余继续倒计时。
九、第三个改进:可视化进度条
等待的时候干着急?加个进度条,让等待”看得见”:
def 进度条(剩余秒数, 总秒数, 名称):
"""画一个文字进度条"""
完成比例 = 1 - 剩余秒数 / 总秒数 # 0.0到1.0之间的小数
宽度 = 20 # 进度条总格数
已满 = int(完成比例 * 宽度) # 已经填了几格
条 = "█" * 已满 + "░" * (宽度 - 已满) # 满的+空的
print(f"r{名称} [{条}] {完成比例 * 100:5.1f}%", end="")
效果:
煮面 [████████░░░░░░░░░░░░] 40.0%
原理全是老知识:
剩余秒数 / 总秒数:剩余比例;1 - 它= 完成比例"█" * 已满:字符串乘以数字 = 重复。第四篇数据类型的小技巧:"哈" * 3→"哈哈哈"完成比例 * 100:5.1f:百分比保留1位小数,占5个字符宽(这样数字跳动时不会左右晃)
要在多个计时器下用,就把显示状态改成对每个计时器调用进度条,并且记住每个计时器的初始总秒数(在字典里加一个”总秒数”字段,添加时记下来)。
十、回顾一下用到了什么
| 知识点 | 在哪里用的 |
|---|
|——–|———–|
| time.sleep() | 暂停1秒、响铃间隔 |
|---|---|
| import time | 导入时间模块 |
| while循环 | 倒计时主循环 |
| for循环 | 遍历计时器、响铃5次 |
| 条件判断 | 到点检查、命令判断 |
| 列表 | 计时器列表、到点记录 |
| 列表.append/remove | 添加/删除计时器 |
| 字典 | 每个计时器的信息 |
| 整除//和取余% | 秒数转”分:秒” |
| 字符串乘法 | 进度条的█和░ |
| try/except | 时长输入验证 |
| f-string格式化 | :02d补零、:.1f小数位 |
| r和end=”” | 原地刷新显示 |
| join | 拼接多计时器简报 |
| exit() | 输入错误直接退出 |
| 函数 | 响亮提醒、添加计时器、进度条 |
十一、一个真实的坑:sleep不精确
用这个提醒器煮面时你可能发现:说好5分钟,它可能5分01秒才喊你。
原因:time.sleep(1)只是”至少睡1秒”。除了这1秒,程序执行print、判断、减法也要花一点点时间(几毫秒)。每次循环多花几毫秒,几百圈下来就慢了几秒。
煮面不差这几秒。但如果你在做科学实验级别的计时,正确做法是用”绝对时间”:记下开始时间time.time(),每次循环用”现在时间 – 开始时间”算已过秒数,而不是靠累加。这样执行再慢,时间也是准的。
今日小练习里就有这个。
十二、今日小练习
练习1:精确计时
把基础版改成”绝对时间”计时:开始时记下开始时刻 = time.time(),循环里用已过 = time.time() - 开始时刻算已过秒数,剩余 = 总秒数 - 已过。对比一下和累加版的差异。
练习2:时分秒输入
现在只支持”几分钟”。改成支持”1小时20分钟”这种输入:让用户分别输入小时和分钟(小时可跳过),换算成总秒数。再想想怎么支持”45秒”的超短计时。
练习3:输入命令解析
多计时器版里,添加计时器和开始倒计时的交互有点笨。重构成:用户输入add 煮面 5就添加一个5分钟的”煮面”计时器(用split()拆字符串,第二十篇练过),输入go开始,输入list看状态。三种命令分发,用if/elif/else。
下一篇:简易记账本 — 每天记一笔花了多少钱,月底看看钱都去哪了。文件存数据、列表记账目、函数做统计——第十二篇价格工具的”理财升级版”。
发表回复