4.3.3 第三层设计

首先考虑函数 leapyears 的实现,该函数的功能是计算从 1900 到 year(不含)之间 的闰年个数。这可以用逐年检验的方法来实现①:对从 1900 到 year-1 的每一年,测试该 年是否闰年,如果是则为计数变量 count 加 1。于是得到如下代码:

def leapyears(year): count = 0
for y in range(1900,year):
    if y%4 == 0 and (y%100 != 0 or y%400 == 0): 
        count = count + 1
    return count

其中 if 语句的布尔表达式是根据闰年的规定得到的:年份能被 4 整除并且不能被 100 整除(除非该年能被 400 整除)。

再考虑函数 heading 的实现,该函数用于打印每个月日历的标题部分(月份和星期名 称)。我们将月份名称放在一个列表中,然后通过传递给 heading 函数的月份值作为索引 来查找月份名称。代码如下:

def heading(m):
    months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"]
    print " %s " % (months[m]) 
    print "Mon Tue Wed Thu Fri Sat Sun"

① 如果从公元 1 年算起,到 year 年为止的闰年个数可用公式 year/4 ? year/100 + year/400 计算。

第三层的最后一个函数是 oneMonth(),其功能是输出一个月的日历。由于日历输出要求在合适的位置上显示合适的日期,这个用于输出的子程序反而是整个程序最费功夫的部 分。为了安排日历布局,需要了解每月 1 日是星期几和每月有多少天,还需要确定何时换行显示。我们采用一个长度为 6×7=42 的列表①作为日历布局框架(每行 7 天,一个月最多占用 6 行),只需将一个月的每一天存入这个框架的合适位置,然后输出这个列表即可。图4.9 是日历框架的示意图。

图 4.9 每个月的日历布局 由于问题有点复杂,我们再次分解任务,用三个子程序来实现 oneMonth():days() 函数计算该月份的天数,layout()函数用于布置该月每一天在日历框架中的位置 ,printMonth()用于输出日历。即:

def oneMonth(year,month,first): 
    d = days(year,month)
    frame = layout(first,d) 
    printMonth(frame)
    return (first + d) % 7

oneMonth 函数有三个参数:year 表示年份,month 表示月份,first 表示该月 1 日是星期几(0~6)。对于一月份,first 由上层模块 printCalendar 的参数 w 提供; 对于其他月份,first 可由上一个月的 first 和天数确定,因此我们让 oneMonth 在打印 本月日历后返回下个月 1 日的星期序号。

① 使用二维列表或许会更直观。

设计至此,结构图演变为图 4.10 所示的情况。

图 4.10 calendar 程序的第三层结构图