Python · Day 14

多个函数协作:
完整成绩报告

第十四天,我不再让一个函数承担全部工作,而是把总分、平均分、等级和最高分拆给不同函数,再用一个总管函数组织它们,完成一份完整的成绩分析报告。

让平均分函数调用总分函数

第一个练习仍然从熟悉的总分和平均分开始,但这次 calculate_average() 不再自己循环累计,而是调用 calculate_total() 取得总分。

day14.py
def calculate_total(scores):                     # 接收成绩列表
    total = 0                                       # 总分从0开始累计
    for score in scores:                          # 逐个取出成绩
        total = total + score                       # 加入已经累计的总分
    return total                                  # 返回最终总分

def calculate_average(scores):                   # 接收同一份成绩列表
    if len(scores) == 0:                          # 先保护空列表
        return 0                                   # 避免后面除以0
    total = calculate_total(scores)                  # 调用另一个函数取得总分
    average = total / len(scores)                    # 计算平均分
    return average                                # 返回浮点数平均分

赋值语句 total = calculate_total(scores) 要先看右侧:程序进入总分函数,得到返回值后,左侧的 total 才接住它。程序整体仍然从上到下执行,只是在遇到函数调用时暂时进入另一个函数。

直接调用与间接调用

当主程序调用 calculate_average() 时,它的内部还会继续调用 calculate_total()。这属于间接调用。今天的小测最初只数到了两处直接调用,随后沿着函数内部的路线重新追踪,确认完整程序中总分函数实际执行了四次。

看到一条函数调用时,不能只数写在主程序里的函数名,还要继续检查被调用的函数内部是否调用了其他函数。

把平均分交给等级函数

平均分计算完成后,返回值继续传入 get_level()。测试列表 [95, 85, 90] 的总分是 270,平均分是浮点数 90.0,因此第一个条件 average >= 90 成立,等级为“优秀”。

day14.py
def get_level(average):                         # 接收已经算好的平均分
    if average >= 90:                            # 测试优秀的边界
        return "优秀"                             # 90.0会进入这里
    elif average >= 80:                          # 前面不满足时再判断良好
        return "良好"                             # 返回等级并结束函数
    elif average >= 60:                          # 继续判断是否及格
        return "及格"                             # 返回及格
    else:                                        # 低于60分
        return "不及格"                           # 返回不及格

/ 并不是固定保留一位小数,而是会返回浮点数;这次刚好显示为 90.0。这个数据随后由形参 average 接收并参与条件判断。

用总管函数组织完整报告

create_report() 不亲自遍历成绩,而是负责安排其他函数工作。每个小函数只做一件事,返回的结果分别由变量接住,最后汇总成字典。

day14.py
def create_report(scores):                         # 定义组织其他函数的总管函数
    total = calculate_total(scores)                  # 接住总分函数的返回值
    average = calculate_average(scores)              # 接住平均分函数的返回值
    level = get_level(average)                       # 把平均分继续传给等级函数
    highest = find_highest_score(scores)             # 接住最高分函数的返回值
    report = {"total": total, "average": average,
              "level": level, "highest": highest} # 汇总字典
    return report                                  # 返回完整报告

final_report = create_report(student_scores)          # 接住返回的字典
print("完整报告:", final_report)                 # 输出四项结果
print("最高分是:", final_report["highest"])      # 按键读取最高分

最终运行结果为 {'total': 270, 'average': 90.0, 'level': '优秀', 'highest': 95}。其中 final_report 是字典,"highest" 是键,读取到的 95 是值。

独立加入最高分功能

最后的独立练习是把昨天掌握的最高分比较重新写进今天的程序。第一次已经正确完成空列表保护、逐项比较和循环外返回,但最高分还在总管函数之外单独输出。订正后,将函数移到调用发生之前,并由 create_report() 调用,最高分才真正成为完整报告的一部分。

整理程序时还发现一条没有继续使用的独立最高分调用。删除它不会改变结果,却能避免重复计算。这让我开始注意:程序写完后,还要检查重复调用和没有使用的变量。

今天的小测与订正

  • calculate_average() 会间接调用 calculate_total()
  • 完整程序中的直接调用和间接调用都要计入执行次数
  • 字典表达式左侧带引号的 "level" 是键,右侧不带引号的 level 是保存结果的变量
  • 执行函数调用前,被使用的函数定义必须已经执行完成
  • 90.0 是浮点数,并且满足 >= 90 的优秀边界

今天真正弄懂了什么

  • 一个函数可以调用另一个函数,并接住它的返回值
  • 函数调用可能继续进入下一层函数,需要沿调用路线追踪
  • 可以把较长任务拆给多个职责清楚的小函数
  • 总管函数可以组织多个函数并汇总它们的结果
  • 字典可以保存总分、平均分、等级和最高分四项信息
  • 测试边界时既要看数值,也要注意浮点数类型
  • 整理代码时应删除重复调用和没有使用的变量

今天的程序第一次超过五十行,但每个函数仍然只负责一小块任务。代码变长并不等于每一部分都变难,关键是沿着调用关系,逐步看清数据从哪里来、被谁接住、最后返回到哪里。