自学一个新模块
4 道练习题·预计 60 分钟·做对一题解锁下一段
前言
各位童鞋,这是基础部分的最后一个实战章了。
前面几章,每个新东西都是教程一步一步讲给你听的。可等你真正自己写程序的时候,会发现要用的模块成百上千,不可能每一个都有人手把手地教。
所以这一章换个学法:咱们自己学会一个没讲过的模块。
学哪个呢?就学 Python 自带的 datetime 。它在第 9 章只是露了个面,演示了怎么创建一个日期。这一章,咱们要用它给记账本的每一笔账加上日期,再算出最近 7 天花了多少钱。
那又该怎么学呢?不靠背,靠这几样:先说清楚要做什么,再去官方文档里找,然后用 dir() 和 help() 看看有哪些工具,最后用几个小例子验证自己的理解。这套办法,换成任何一个模块都管用。
自学的时候,看不懂文档,或者试出来的结果和想的不一样,都是常事。碰到这种情况,别急着去搜现成的答案,先自己多试几次。因为自己试出来的东西,记得最牢。
学这一章之前,需要先学完 实战 5 的记账本 v4,以及 第 9 章 的「学会用一个陌生模块」和「几个常用的标准库」。
目录
这一章会学到:
- 一、账单缺了日期——光知道花了多少,不知道是哪天花的
- 二、自学一个模块,分四步——先列需求,再找文档
- 三、今天是几号?——
date、date.today()和fromisoformat() - 四、7 天前是哪天?——用
timedelta算日期 - 五、显示成「11月02日」——
strftime()和格式代码 - 六、常见坑
- 七、自己查一次文档——一周从星期几算起?
- 八、记账本 v5
- 九、小结与回看
学完这一章,你的记账本会给每一笔账记上日期,还能算出最近 7 天花了多少钱;更重要的是,你会有一套自学新模块的办法。
一、账单缺了日期
上一章,记账本 v4 已经能把账存进文件了。可是打开 my_ledger.json 看一看,每一笔账只有三样东西:
{
"item": "奶茶",
"category": "餐饮",
"cost": 12.5
}买了什么、什么类别、花了多少,唯独没有哪天花的。
少了日期,会有什么麻烦呢?假如账记了一个月,想知道这个星期花了多少、哪天花得最多,就没法算了。
所以这一章要给每一笔账加上日期。具体来说,要做到这几件事:
- 记账的时候,自动记下今天的日期。
- 日期能存进 json 文件,下次还能读回来。
- 算出「7 天前」是哪一天。
- 判断某一笔账是不是在最近 7 天里。
- 显示的时候,写成「11月02日」这样好看的样子。
这些事,Python 自带的 datetime 模块都能做。可它到底怎么用,前面没讲过。
这回,咱们自己学。怎么学呢?下一节先说说自学一个模块的办法。
二、自学一个模块,分四步
其实自学一个模块的办法,第 9 章「学会用一个陌生模块」里已经用 math.ceil 演示过一回:先明确需求,再去文档里找,然后用正数、零和负数验证。这一章把它展开成四步:
- 列需求:用大白话写清楚要做什么。上一节已经列好了。
- 找文档:去官方文档找到这个模块的那一页,先看目录,知道它大概有哪些东西。
- 小步试:用
dir()看看有哪些方法,用help()看说明,每个方法写两三行代码试一试。 - 用边界验证:拿特殊的值去试,比如月底、年底、闰年,看结果是不是和你想的一样。
第一步上一节已经做完了,接下来做第二步:找文档。datetime 模块的官方中文文档在这里:datetime --- 基本日期和时间类型 。
这一页很长,别想着从头读到尾。先看开头的介绍和目录,能找到这几个名字:
date:日期,年、月、日。time:一天里的时间,时、分、秒。datetime:日期加时间。timedelta:两个日期或时间之间的差,比如「7 天」。
四个名字里,该先看哪个呢?记账只关心哪一天,不关心几点几分,所以先盯住 date 。这就到了第三步,先用 dir() 看看它都有些什么:
from datetime import date
print([name for name in dir(date) if not name.startswith('_')])输出的结果:
['ctime', 'day', 'fromisocalendar', 'fromisoformat', 'fromordinal', 'fromtimestamp', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'min', 'month', 'replace', 'resolution', 'strftime', 'timetuple', 'today', 'toordinal', 'weekday', 'year']代码里那一行列表生成式,是干什么的呢?因为 dir() 列出来的名字里,有一大堆以下划线开头的,那是 Python 内部用的。所以这里用第 7 章的列表生成式,把它们筛掉了。
筛完还剩二十个名字。要全学吗?不用。对着上一节列的需求,挑出看着有关系的就行:
| 需求 | 看着有关系的名字 |
|---|---|
| 拿到今天的日期 | today |
| 存进文件、再读回来 | isoformat 、fromisoformat |
| 年、月、日 | year 、month 、day |
| 星期几 | weekday 、isoweekday |
| 显示成好看的样子 | strftime |
那「7 天前」这个需求呢?date 里没有看着像的名字。不过别忘了,文档目录里还有 timedelta ,名字里的 delta 就是「差值」的意思,应该就是它了。
清单列好了,接下来就照着它,一个一个地试。
三、今天是几号?
清单上的第一个需求,是拿到今天的日期,所以先试 today 。看看说明书怎么说:
from datetime import date
help(date.today)说明书的主要内容是这样的:
today(...) class method of datetime.date
Current date or datetime: same as self.__class__.fromtimestamp(time.time()).说明书里的 Current date ,就是当前的日期。文档里写得更直白:返回当前的本地日期。到底是不是这样,试一下就知道了:
from datetime import date
print(date.today())
print(type(date.today()))输出的结果(第一行是你运行那天的日期):
2026-11-02
<class 'datetime.date'>再看第二行:拿到的不是字符串,而是一个 date 对象。
不过有个问题:date.today() 每天运行的结果都不一样,拿来做例子不方便。所以下面的例子,都用 date(年, 月, 日) 创建一个固定的日期。这种写法,第 9 章见过:
from datetime import date
d = date(2026, 11, 2)
print(d)
print(d.year, d.month, d.day)输出的结果:
2026-11-02
2026 11 2从输出可以看到,用 d.year 、d.month 、d.day ,就能分别拿到年、月、日。
日期拿到了,接下来看清单上的「存进文件、再读回来」。实战 5 说过,json 只认数字、字符串、列表、字典这些东西。那日期该怎么存呢?可以先把日期变成字符串存进去,读回来的时候,再变回日期。
清单里的 isoformat() 和 fromisoformat() ,名字刚好是一对,看着就像是干这个的。先看一眼 fromisoformat() 的说明:
from datetime import date
help(date.fromisoformat)说明书的主要内容是这样的:
fromisoformat(...) class method of datetime.date
str -> Construct a date from a string in ISO 8601 format.开头的 str -> ,意思是给它一个字符串,它造出一个日期。那要什么样的字符串呢?后面写着:ISO 8601 格式。ISO 8601 是一种国际通用的日期写法,就是 2026-11-02 这样的「年-月-日」。
说明看懂了,就动手试一试,看日期能不能变成字符串,再变回来:
from datetime import date
d = date(2026, 11, 2)
text = d.isoformat()
print(text, type(text))
back = date.fromisoformat(text)
print(back, type(back))
print(back == d)输出的结果:
2026-11-02 <class 'str'>
2026-11-02 <class 'datetime.date'>
True对照三行输出来看:
- 第一行:
isoformat()把日期变成了字符串'2026-11-02'。 - 第二行:
fromisoformat()再把它变回了日期。 - 第三行:
True说明,变回来的日期和原来的一模一样。
这样一来,存文件的问题就解决了。清单上接下来是「7 天前是哪一天」,下一节接着试。
四、7 天前是哪天?
第二节猜过,「7 天前」这个需求要靠 timedelta 。文档里它的样子是 timedelta(days=0, seconds=0, ...) ,参数一长串,咱们只用得到 days 。
猜一下:日期减去 timedelta(days=7) ,是不是就是 7 天前?试试:
from datetime import date, timedelta
d = date(2026, 11, 2)
print(d - timedelta(days=7))
print(d + timedelta(days=30))输出的结果:
2026-10-26
2026-12-02猜对了。而且加 30 天的时候,它自己知道 11 月只有 30 天,算到了 12 月 2 日,不用咱们操心每个月有几天。
不过,只试一两个普通的日期,还不够放心。这就到了四步里的第四步:用边界验证。日期最容易出错的就是月底、年底和闰年,挑几个试试:
from datetime import date, timedelta
print(date(2026, 3, 1) - timedelta(days=1))
print(date(2028, 3, 1) - timedelta(days=1))
print(date(2026, 12, 31) + timedelta(days=1))输出的结果:
2026-02-28
2028-02-29
2027-01-01一行一行看:2026 年不是闰年,3 月 1 日的前一天是 2 月 28 日。2028 年是闰年,前一天就是 2 月 29 日。年底再加一天,自动跨到了下一年。三个都对。
日期减去几天,得到的还是日期。那反过来,两个日期相减,得到的又是什么呢?
from datetime import date
gap = date(2026, 12, 25) - date(2026, 11, 2)
print(gap)
print(type(gap))
print(gap.days)输出的结果:
53 days, 0:00:00
<class 'datetime.timedelta'>
53看第二行就知道,两个日期相减,得到的也是一个 timedelta 。要拿到相差几天,用它的 .days ,也就是第三行的 53 。
再看清单上的下一个需求:判断某一笔账是不是在最近 7 天里。这要用到日期的另一个本事:日期之间还能直接比大小,越晚的越大:
from datetime import date, timedelta
today = date(2026, 11, 2)
week_ago = today - timedelta(days=7)
print(date(2026, 10, 30) >= week_ago)
print(date(2026, 10, 20) >= week_ago)输出的结果:
True
False代码先算出 7 天前是哪天,再拿两个日期和它比。10 月 30 日在最近 7 天里,10 月 20 日不在。这样一来,「判断某一笔账是不是在最近 7 天里」,这个需求也解决了。
日期的加减和比较都试过了,先做一道练习:读入两个日期,算出它们相差几天。
读入两个 2026-11-02 这样格式的日期,打印它们相差几天。
「程序输入」框里预填了 2026-11-02 和 2026-12-25,运行后完整的输出应该是:
开始日期:2026-11-02
结束日期:2026-12-25
相差 53 天检查答案时还会用跨过 2 月底的日期,以及两个相同的日期。
「运行」使用上面的输入;「检查答案」会依次使用题目给定的 3 组输入。
推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai