第2章 2.5 综合实战:FizzBuzz + 倒计时器
📌 上一章我们学了箭头函数的两种写法、
this的四大绑定规则,你现在应该能看懂大部分 JS 代码里的=>了。但光会看不够——这一章我们要用前面的知识点,做两个真实好玩的小工具,让你体会「原来我的代码真的能跑起来!」的成就感。
🎯 开场 3 分钟:为什么要学这个?
你有没有遇到过这些情况?
- 写了个小程序,运行的时候啥反馈都没有——不知道它跑到哪了,也不知道对不对
- 看到一个经典的 FizzBuzz 面试题,脑子里想得挺好,但写出来一堆 if-else 乱成一团
- 想给女朋友/男朋友倒计时一个重要日子,结果网上找的工具都有广告
这一章,两个问题一起解决。
我们先用 FizzBuzz 练手,学会把逻辑拆成小块、用循环+条件配合;然后做一个「倒计时器」,学会处理日期、做减法、格式化输出。
学完这章,你就能:
- ✅ 写出一个带进度条的循环程序
- ✅ 做出一个可以计算任意目标日期倒计\n\n
\n\n
\n\n时的小工具 - ✅ 掌握在循环里打印状态的技巧(妈妈再也不用担心我的程序「死机」了)
🧱 基础 25 分钟:核心概念(小白视角)
什么是 FizzBuzz?
FizzBuzz 是一个经典的编程入门题,规则超简单:
从 1 数到 100
- 如果能被 3 整除,说 "Fizz"
- 如果能被 5 整除,说 "Buzz"
- 如果能同时被 3 和 5 整除,说 "FizzBuzz"
- 否则,说出这个数字
看起来很简单对吧?但面试里 90% 的人第一次写都有 bug。我们先讲为什么。
生活类比:把数字分类放袋子
想象你有一堆积木,要分类放进三个盒子:
- 盒子 A:能被 3 整除的 → 贴 "Fizz" 标签
- 盒子 B:能被 5 整除的 → 贴 "Buzz" 标签
- 盒子 C:两个都能整除的 → 贴 "FizzBuzz" 标签(优先级最高)
关键点:先检查「两个都能整除」,再检查其他的。不然 15 会被错误地放进 A 或 B,不会进 C。
第一步:写最直观的版本
for i in range(1, 101):
if i % 15 == 0: # 先检查能不能被 3 和 5 同时整除
print("FizzBuzz")
elif i % 3 == 0: # 再检查能不能被 3 整除
print("Fizz")
elif i % 5 == 0: # 最后检查能不能被 5 整除
print("Buzz")
else: # 都不行就输出数字本身
print(i)
这行在干嘛:
- range(1, 101) 生成 1 到 100 的数字
- % 是取余数运算符,15 % 3 == 0 就是「15 能被 3 整除」
- elif = else if,先不满足前面的才检查这个
运行一下试试!输出前 20 行大概长这样:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
第二步:进阶写法——用函数封装
为什么要封装成函数?
类比一下:就像你做菜,把「打鸡蛋」这个动作封装成一个步骤,下次做别的菜也能用,不用每次都重新想怎么打。
def fizzbuzz(n):
"""判断一个数 n 应该输出什么"""
if n % 15 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
else:
return str(n)
# 调用函数,生成 1-100
for i in range(1, 101):
print(fizzbuzz(i))
这行在干嘛:
- def fizzbuzz(n): 定义一个函数,输入数字 n,返回对应的字符串
- str(n) 把数字转成字符串,这样 print 的时候输出格式统一
第三步:倒计时器——处理日期
学会了 FizzBuzz,我们来做倒计时器。
核心思路: 两个日期相减,得到相差的天数/小时数/分钟数。
from datetime import datetime
# 设定目标日期(女朋友生日)
target_date = datetime(2025, 12, 25, 0, 0, 0)
# 获取当前时间
now = datetime.now()
# 计算差值
diff = target_date - now
print(f"距离目标日期还有 {diff.days} 天")
print(f"换算成小时是 {diff.total_seconds() / 3600:.1f} 小时")
这行在干嘛:
- datetime 是 Python 里处理日期时间的工具
- datetime.now() 获取此时此刻的时间
- 两个 datetime 相减得到 timedelta 对象,里面有天数、秒数等信息
输出大概长这样:
距离目标日期还有 182 天
换算成小时是 4371.5 小时
第四步:把 FizzBuzz 和倒计时组合——带进度条的倒计时
核心概念:进度条
想象你在下载文件,那个 progress bar 怎么实现的?
原理:在同一行不断覆盖打印。Python 里用 \r 实现「回车」(回到行首),用 end='' 阻止自动换行。
import time
from datetime import datetime
def countdown_with_progress(target_date):
"""带进度条的倒计时器"""
while True:
now = datetime.now()
diff = target_date - now
if diff.total_seconds() <= 0:
print("\n时间到!🎉")
break
# 计算进度(假设总倒计时 100 天)
total_seconds = 86400 * 100 # 100 天
progress = (total_seconds - diff.total_seconds()) / total_seconds * 100
# \r 回到行首,end='' 不换行,这样就能覆盖之前的内容
print(f"\r倒计时: {diff.days} 天 {diff.seconds // 3600} 小时 | 进度: {progress:.1f}%", end='')
time.sleep(0.5) # 暂停 0.5 秒,别让电脑累死
# 测试:设定 1 分钟后的时间
test_target = datetime.now().replace(second=0, microsecond=0)
test_target = test_target.replace(minute=test_target.minute + 1)
print("开始测试倒计时(1分钟后到期)...")
countdown_with_progress(test_target)
这行在干嘛:
- time.sleep(0.5) 让程序暂停半秒,不然它一秒能跑几万次
- \r 是「回车符」,让光标回到行首
- end='' 告诉 print 别换行,这样下一次 print 就会覆盖上一次
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):一行代码的 FizzBuzz
目标: 学会用列表推导式写 FizzBuzz
# 用列表推导式 + 三元表达式,一行搞定
result = ["FizzBuzz" if i % 15 == 0 else "Fizz" if i % 3 == 0 else "Buzz" if i % 5 == 0 else str(i) for i in range(1, 101)]
# 打印前 20 个
for item in result[:20]:
print(item)
预期输出:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
一句话解释: 列表推导式就是「把 for 循环的结果直接装进列表」的简写语法。
项目 2(15 分钟):从 CSV 读取生日清单,给每个人倒计时
目标: 学会读取文件 + 处理日期 + 格式化输出
假设你有一个 birthdays.csv 文件,内容如下:
名字,生日
小明,1995-03-15
小红,1998-07-22
小刚,2000-01-01
完整代码:
import csv
from datetime import datetime
def read_birthdays(filename):
"""从 CSV 读取生日数据"""
birthdays = []
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f) # 用字典方式读取,每行是一个 {列名: 值}
for row in reader:
birthdays.append({
'name': row['名字'],
'birthday': datetime.strptime(row['生日'], '%Y-%m-%d')
})
return birthdays
def days_until(birthday_date):
"""计算距离下次生日还有多少天"""
today = datetime.now()
# 把今年的生日日期算出来
this_year_birthday = birthday_date.replace(year=today.year)
# 如果今年的生日已经过了,算明年的
if this_year_birthday < today:
this_year_birthday = this_year_birthday.replace(year=today.year + 1)
diff = this_year_birthday - today
return diff.days
# 读取文件并计算(需要先创建 birthdays.csv)
try:
birthdays = read_birthdays('birthdays.csv')
print("📅 生日倒计时清单")
print("=" * 30)
for person in birthdays:
days = days_until(person['birthday'])
print(f"{person['name']}: {days} 天后过生日")
except FileNotFoundError:
print("⚠️ 请先创建 birthdays.csv 文件(内容见上文)")
预期输出:
📅 生日倒计时清单
==============================
小明: 123 天后过生日
小红: 89 天后过生日
小刚: 201 天后过生日
一句话解释: csv.DictReader 让你像查字典一样读取 CSV,每列有个名字。
项目 3(15 分钟):做一个「发呆计时器」——工作 25 分钟,休息 5 分钟
目标: 综合运用循环、条件、倒计时,做一个番茄钟
import time
from datetime import datetime, timedelta
def pomodoro_timer(work_minutes=25, break_minutes=5, rounds=4):
"""
番茄钟:工作 -> 休息 -> 工作 -> 休息 ...
- work_minutes: 工作时长(分钟)
- break_minutes: 休息时长(分钟)
- rounds: 几轮
"""
print(f"🍅 番茄钟开始!工作 {work_minutes} 分钟,休息 {break_minutes} 分钟,共 {rounds} 轮")
print("=" * 40)
for round_num in range(1, rounds + 1):
# 工作时段
print(f"\n🔔 第 {round_num} 轮 - 开始工作!")
end_time = datetime.now() + timedelta(minutes=work_minutes)
while datetime.now() < end_time:
remaining = (end_time - datetime.now()).seconds
mins = remaining // 60
secs = remaining % 60
print(f"\r工作倒计时: {mins:02d}:{secs:02d}", end='')
time.sleep(1)
print(f"\n✅ 第 {round_num} 轮工作完成!")
# 休息时段(最后一轮不休息)
if round_num < rounds:
print(f"☕ 开始休息 {break_minutes} 分钟...")
break_end = datetime.now() + timedelta(minutes=break_minutes)
while datetime.now() < break_end:
remaining = (break_end - datetime.now()).seconds
mins = remaining // 60
secs = remaining % 60
print(f"\r休息倒计时: {mins:02d}:{secs:02d}", end='')
time.sleep(1)
print(f"\n🎉 休息结束!")
print("\n" + "=" * 40)
print("🍅 今天的番茄钟完成了!给自己点个赞 👍")
# 运行番茄钟(演示用,改成 1 分钟工作 + 10 秒休息)
pomodoro_timer(work_minutes=1, break_minutes=10, rounds=2)
预期输出(截取部分):
🍅 番茄钟开始!工作 1 分钟,休息 10 秒,共 2 轮
========================================
🔔 第 1 轮 - 开始工作!
工作倒计时: 00:58
工作倒计时: 00:45
...
✅ 第 1 轮工作完成!
☕ 开始休息 10 秒...
休息倒计时: 00:07
...
🎉 休息结束!
🔔 第 2 轮 - 开始工作!
...
🍅 今天的番茄钟完成了!给自己点个赞 👍
一句话解释: timedelta 就像「给日期做加减法」的工具,5 分钟 = timedelta(minutes=5)。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:忘记检查优先级
# ❌ 错误:先检查 3,再检查 5,15 永远进不去 FizzBuzz 分支
if i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
elif i % 15 == 0: # 这个永远执行不到!
print("FizzBuzz")
# ✅ 正确:先检查 15
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
为什么错: if-else 是「短路」机制,一旦前面满足了,后面就不看了。
坑 2:datetime 的坑——字符串格式不对
# ❌ 错误:格式不匹配会报错
datetime.strptime("1995/03/15", '%Y-%m-%d') # 报错!
# ✅ 正确:格式要一致
datetime.strptime("1995-03-15", '%Y-%m-%d') # 正常
datetime.strptime("1995/03/15", '%Y/%m/%d') # 正常
常见格式符号:
- %Y = 4 位年份(如 2025)
- %m = 2 位月份(如 03)
- %d = 2 位日期(如 15)
- %H = 24 小时(如 14)
- %M = 分钟(如 05)
- %S = 秒(如 30)
坑 3:time.sleep 的坑——阻塞主线程
# ❌ 错误:在 GUI 程序里用 sleep 会让界面卡死
def bad_countdown():
for i in range(10, 0, -1):
print(i)
time.sleep(1) # 这一秒程序什么都做不了
# ✅ 正确:用 threading 或者 tkinter 的 after 方法
import threading
def countdown_thread():
for i in range(10, 0, -1):
print(i)
time.sleep(1)
# 在后台线程运行,不阻塞主程序
thread = threading.Thread(target=countdown_thread)
thread.start()
坑 4:print 的 end='' 没加,导致换行混乱
# ❌ 错误:不加 end='',每个数字都换行
for i in range(1, 101):
print(i) # 默认 end='\n',每个输出都换行
# ✅ 正确:进度条要用 end=''
for i in range(1, 101):
print(f"\r进度: {i}%", end='') # 同一行覆盖
坑 5:循环里修改正在迭代的列表
# ❌ 错误:边迭代边删除会漏元素
nums = [1, 2, 3, 4, 5]
for n in nums:
if n % 2 == 0:
nums.remove(n) # 危险!
# ✅ 正确:遍历副本,或者用列表推导式生成新列表
nums = [1, 2, 3, 4, 5]
nums = [n for n in nums if n % 2 != 0] # 只保留奇数
性能小贴士:字符串拼接用 join 不用 +
# ❌ 慢:每次 + 都会创建新字符串
result = ""
for i in range(1000):
result += str(i) # 创建了 1000 个字符串对象
# ✅ 快:join 一次搞定
result = "".join(str(i) for i in range(1000))
调试技巧:用 f-string 加变量名,打印中间状态
# 遇到 bug 时,在关键地方打印变量值
def fizzbuzz(n):
print(f"[DEBUG] 输入 n={n}") # 方便定位问题
if n % 15 == 0:
return "FizzBuzz"
...
✏️ 练习题 + 作业题(共 7 分钟)
练习题(5 道,10 分钟内完成)
练习 1(2 分钟):改数字
- 输入:把 FizzBuzz 的范围从 1-100 改成 1-50
- 预期输出:只输出到 50,其他规则不变
- 提示:range(1, 101) 改成 range(1, 51)
练习 2(2 分钟):加一个判断
- 输入:在 FizzBuzz 基础上,如果数字是 7 的倍数,输出 "Boom"
- 预期输出:7 → "Boom",14 → "FizzBuzz" 或 "Boom"(选一个规则)
- 提示:先想好优先级,在 elif i % 3 == 0 前面加一行
练习 3(2 分钟):处理新数据
- 输入:给 birthdays.csv 加一个人,出生日期任意
- 预期输出:程序能正确显示新加的人的倒计时
- 提示:直接用 Excel 或记事本打开 CSV 文件,在最后一行加
练习 4(3 分钟):串起来
- 输入:用 FizzBuzz 的逻辑,统计 1-100 里有多少个 "Fizz"、"Buzz"、"FizzBuzz"
- 预期输出:Fizz: 27, Buzz: 14, FizzBuzz: 6, 数字: 53
- 提示:用一个字典 {"Fizz": 0, "Buzz": 0, ...} 来计数
练习 5(1 分钟):读报错
- 输入:运行下面代码,看报什么错
from datetime import datetime
d = datetime.strptime("2025-13-01", "%Y-%m-%d")
- 预期输出:说出错误原因
- 提示:月份没有 13
作业题(30 分钟-2 小时)
作业:做一个「纪念日倒计时器」
- 需求描述: 给定一个纪念日名称和日期,计算并显示距离今天还有多久,精确到天/小时/分钟/秒
- 功能点:
1. 从用户输入读取纪念日名称和日期
2. 计算倒计时,格式化成「XX 天 XX 小时 XX 分 XX 秒」
3. 每秒更新一次显示(用\r覆盖)
4. 到期后显示「到了!🎉」并退出 - 加分项:
1. 支持多个纪念日一起倒计时(列表)
2. 把数据存到文件里,程序重启不丢失 - 验收标准: 能跑起来 + 每秒更新显示 + 到期提示
- 提交方式: 评论区贴代码或 GitHub 链接
📚 总结 + 资源(5 分钟)
本文学了 3 个核心点:
- FizzBuzz 是 if-elif 优先级训练的经典题——先检查范围小的条件
- datetime 是处理日期时间的利器——
strptime解析字符串,timedelta做日期加减 \r+end=''可以做出进度条效果——让程序看起来更专业
延伸学习资源:
- 📖 官方文档:datetime — 官方文档,每个方法都有例子
- 📖 《Python 编程:从入门到实践》——第 8 章「函数」+ 第 10 章「文件和异常」
- 🎬 B 站搜索「Python 倒计时 进度条」——有很多可视化教程
互动钩子:
🎯 你有没有什么特别的日子需要倒计时?生日、纪念日、假期倒计时?评论区说说,下一章我们会学到
map/filter/reduce,用它来批量处理多个倒计时,让代码更优雅!
(全文约 5100 字)

评论(0)