今天来记录一下第五十九题的内容,分了两个版本,一个是原始版本,一个是优化版本。
首先是原始版本:
#编写一个程序,要求用户输入年份和月份,打印该月份对应的日历
#导入日历模块 calendar
import calendar
#提示用户输入年份并将其转换为整型
year = int(input('请输入年份:'))
#提示用户输入月份并将其转换为整型
month = int(input('请输入月份:'))
print(calendar.month(year, month))
这个代码非常简单,甚至有些简陋,下面是这个代码块的思路:
题目要求:用户输入年份和月份,打印该月对应的日历。
我的思路:造日历这事儿,早有人替我写好了
第一眼曾想过要不要自己算"某年某月1号是星期几、这个月有几天"------那得动用取余、闰年判断一堆数学。但立刻想起 48 题以来的老规矩:先翻翻标准库有没有轮子。果然有:calendar 模块,而且是 Python 自带、不用 pip 安装的官方轮子(和 58 题的 dateutil 那种第三方不同)。
程序短到就三步:问年份、问月份、交给 calendar.month()。前两步还是老熟人 int(input())------输入必须转整数才能进日历函数的"门槛";最后这个函数直接返回一张排版整齐的多行日历字符串,外面套一层 print 就是成品,星期表头、日期对齐全都不用我操心。
我发现的两个小彩蛋
一是 calendar.month() 是"哑巴"------它不打印,只返回字符串,所以我必须自己套 print;模块里另有个 prmonth(p 代表 print)会直接打印,两种风格二选一。二是它默认把星期一排在表头第一个,和国外周日开头的习惯不同,这背后是 Python 的"周从周一记起"约定。
隐患照旧:裸奔的输入
这题又双叒没做验证:输月份 13,calendar.month 会直接抛 IllegalMonthError 崩给你看。从 49 题验证码到 53 题区间、再到今天,"功能一行流、输入零防护"已是这批题目的通病。真要加固,43/44 题的 while True + try/except 加一条 1 <= month <= 12 的范围检查就是标准答案------模块轮子再好用,也替不了你把关进门输入。
接下来是优化版本:
# 编写一个程序,要求用户输入年份和月份,打印该月份对应的日历(优化版)
# 优化点1:输入验证——非整数、越界年份/月份都不再崩溃,而是重新提示输入
# 优化点2:支持连续查询——打印完日历后可选择继续查询或退出程序
# 导入日历模块
import calendar
# 定义函数:反复询问直到用户输入一个合法整数(沿用43题 while True + try/except 模式)
def get_int(prompt):
"""
获取用户输入并验证是否为整数
:param prompt: 提示文字
:return: 合法整数
"""
while True:
try:
return int(input(prompt))
except ValueError:
print('输入错误,请输入一个整数。')
# 定义函数:获取年份,并检查合理范围
def get_year():
while True:
year = get_int('请输入年份(如 2026):')
if 1 <= year <= 9999: # calendar 模块只支持有效年份,越界会报错
return year
print('年份越界,请输入 1 到 9999 之间的年份。')
# 定义函数:获取月份,并检查 1-12 范围(修复原版输 13 直接崩溃的缺陷)
def get_month():
while True:
month = get_int('请输入月份(1-12):')
if 1 <= month <= 12:
return month
print('月份应在 1 到 12 之间,请重新输入。')
# 外层循环支撑多次查询(沿用56优化版的菜单常驻模式)
while True:
year = get_year()
month = get_month()
print(calendar.month(year, month))
choice = input('是否继续查询其他月份?(按 y 继续 / 按 n 结束):')
if choice.lower() != 'y':
break
print('感谢使用,再见!')
这个优化版本的代码量对比原始版本确实多了非常多,下面是一些思路分析:
优化目标:修复原版"输入什么都敢直接算、查一次就退出"的两个缺陷。
原版的两块短板
原版代码只有干净三行:问年份、问月份、把结果塞给 calendar.month() 打印。它在我输得规规矩矩时确实好用,但我一试就露馅:输入字母,int() 当场抛 ValueError 掀桌;输入 13 月,calendar 抛 IllegalMonthError 照样崩;而且查完一个月程序就走完了,想看下一月得重新运行。
所以我的优化不碰"画日历"这件本身已经交给标准库的事,只补输入这一环和用完这一环。
优化一:把 43/44 题的验证套路抽成可复用函数
我没有在外面写一堆 if 保护,而是把"非要问你拿到一个合法整数不可"封装成 get_int():循环里 try 住转换,失败就提示后自然进入下一轮,成功才 return 结束循环。这正是 43 题那套 while True + try/except,只是这次我把它当零件用,年、月两处都能调,不必复制粘贴验证代码。
关键是范围检查单独用 if 放在 try/except 之外------这是 44 题的教训:能转成整数和数字合不合规矩是两道关卡,混在一个 try 里容易误伤。于是 get_month() 卡 1~12,get_year() 卡 1~9999(calendar 支持的合法年份),越界就重新问,永远不把非法值送进日历函数去炸。
优化二:外层循环当菜单,查完不用重启
结构上我套了一层大循环,把"问年月 → 打日历 → 问要不要继续"做成常驻菜单,选继续就 continue 回到开头,选结束才 break 走人。这和 56 题优化版的秒表菜单是同一个思路:该反复做的流程用外层循环兜住,而不是让用户反复运行程序。
判断继续与否时,我把条件写成"不是 y 就退出",而不是"是 y 才继续"。因为用户很可能顺手敲回车或输别的字符,负向判断能让所有意外输入统一落到"安全结束"那一侧,不会卡在循环里莫名其妙。
顺带的结构收益
原版是三行流水账,优化后拆成了三个小函数,每个只管"获取 + 验证"一件事。这一步其实提前摸到了第四章函数化的门道:把重复的逻辑封装起来,主流程读起来就只剩"取值、打印、问续用"三步,验证的脏活累活全藏在函数里。