实验3.函数与模块综合实验
函数与模块综合实验
本实验把函数定义与调用、参数形态、返回值、Lambda、高阶函数、递归、简单装饰器整合成一个“成绩工具箱”,强调“先拆函数,再组装流程”。
实验目标
- 会把重复逻辑抽成函数,用默认参数和关键字参数降低调用成本。
- 会使用
*args接收可变数量成绩,并正确返回统计结果(元组/字典)。 - 会用
lambda+sorted/filter做轻量数据处理。 - 会写带递归出口的递归函数(阶乘、斐波那契)。
- 会写一个最简单的装饰器,给函数统一加日志。
模块划分建议
虽然本实验写在单个文件中演示,但请按“模块”的思维组织代码,将来可直接拆到 score_tools.py:
| 函数 | 职责 |
|---|---|
is_valid_score |
校验单科成绩是否在 0~100 |
average |
对可变参数求平均 |
grade_of |
百分制转等级 |
stat_scores |
返回最高/最低/平均/及格人数 |
top_n |
返回分数最高的前 N 名 |
factorial |
递归求阶乘(演示递归) |
log_call |
装饰器:打印“调用了谁” |
参考实现
"""
成绩工具箱
覆盖:函数参数、返回值、Lambda、递归、装饰器
"""
# ---------- 装饰器 ----------
def log_call(func):
def wrapper(*args, **kwargs):
print(f'[日志] 调用 {func.__name__}{args}')
result = func(*args, **kwargs)
return result
return wrapper
# ---------- 基础校验与计算 ----------
def is_valid_score(score, full_mark=100):
"""判断成绩是否有效;full_mark 可按满分调整。"""
return isinstance(score, (int, float)) and 0 <= score <= full_mark
@log_call
def average(*scores):
"""接收 0 个或多个成绩,返回平均分;没有成绩时返回 0。"""
if not scores:
return 0
return sum(scores) / len(scores)
def grade_of(score):
if score >= 90:
return 'A'
if score >= 80:
return 'B'
if score >= 70:
return 'C'
if score >= 60:
return 'D'
return 'F'
def stat_scores(scores):
"""一次性返回多种统计信息,调用方用键取用。"""
if not scores:
return {
'count': 0,
'max': None,
'min': None,
'average': 0,
'pass_count': 0,
}
return {
'count': len(scores),
'max': max(scores),
'min': min(scores),
'average': average(*scores),
'pass_count': sum(1 for s in scores if s >= 60),
}
def top_n(students, n=3, key=None):
"""students: 列表,元素为 (name, score) 元组。"""
if key is None:
key = lambda item: item[1]
return sorted(students, key=key, reverse=True)[:n]
# ---------- 递归 ----------
def factorial(n):
if n < 0:
raise ValueError('n 必须是非负整数')
if n == 0 or n == 1:
return 1
return n * factorial(n - 1)
def fib(n):
if n <= 0:
return 0
if n == 1:
return 1
return fib(n - 1) + fib(n - 2)
# ---------- 组装流程 ----------
def main():
raw_scores = [95, 82, 67, 45, 78, 90]
valid_scores = [s for s in raw_scores if is_valid_score(s)]
print('有效成绩:', valid_scores)
print('平均分:', round(average(*valid_scores), 2))
print('等级:', [grade_of(s) for s in valid_scores])
info = stat_scores(valid_scores)
print('统计:', info)
students = [
('张三', 95),
('李四', 82),
('王五', 67),
('赵六', 45),
('钱七', 78),
]
print('前三名:', top_n(students, n=3))
# lambda + filter:筛出及格
passed = list(filter(lambda x: x[1] >= 60, students))
print('及格名单:', passed)
print('5! =', factorial(5))
print('fib(8) =', fib(8))
if __name__ == '__main__':
main()预期输出
有效成绩: [95, 82, 67, 45, 78, 90]
[日志] 调用 average(95, 82, 67, 45, 78, 90)
平均分: 76.17
等级: ['A', 'B', 'D', 'F', 'C', 'A']
[日志] 调用 average(95, 82, 67, 45, 78, 90)
统计:{'count': 6, 'max': 95, 'min': 45, 'average': 76.166..., 'pass_count': 5}
前三名: [('张三', 95), ('李四', 82), ('钱七', 78)]
及格名单: [('张三', 95), ('李四', 82), ('王五', 67), ('钱七', 78)]
5! = 120
fib(8) = 21说明:
stat_scores内部会再次调用被装饰的average,因此日志会出现两次。
扩展挑战
- 把工具函数拆到
score_tools.py,主程序用from score_tools import average导入,体会“模块管理函数”。 - 给
top_n增加关键字参数reverse=False时求最低分前 N 名。 - 把
log_call增强为可记录耗时(提示:time.perf_counter())。
知识点总结
- 定义与调用:
def定义,圆括号调用;函数名见名知意;用文档字符串说明“自变量—因变量”关系。 - 参数形态:位置参数按顺序传入;默认参数
full_mark=100、n=3简化常见调用;关键字参数可读性更好;*scores聚合成元组,average(*valid_scores)是把列表打散再传入。 - 返回值:单值用
return 表达式;多种信息用字典返回,避免调用方依赖脆弱的下标;没有成绩时返回合理默认值而不是让除零发生。 - 高阶函数与 Lambda:
sorted(key=...)、filter都接受函数;lambda item: item[1]适合一行完成的简单规则,复杂逻辑仍应用def。 - 递归:必须有递归出口(
n==0 or n==1);每层把问题变小(n * factorial(n-1));非法输入用raise明确报错。 - 装饰器:
log_call返回wrapper,从而在不改原函数源码的前提下增加日志;理解“函数可以当参数、当返回值”是进阶关键。 - 程序入口:
if __name__ == '__main__':保证被导入时不自动跑演示逻辑,这是模块化的重要习惯。 - 易错点:忘记
return得到None;把可变默认参数写成def f(x=[])的隐患;递归缺少出口导致栈溢出;装饰器忘记写成return wrapper或调用时多写一层括号。 - 学习重点:能主动把实验1里的“一大段菜单代码”拆成职责单一的函数,并保证调用关系清晰、返回值稳定。