学习前提

理解列表、循环与变量;会用表达式计算结果。

本课完成后你能做什么

把计算与输出分开,避免可变默认参数共享状态。

先理解机制

def定义函数,调用时才执行函数体。参数是输入,return是输出;没有返回值的路径会得到None。把格式化规则做成纯函数,比在多个地方复制print表达式更容易验证和修改。

位置参数适合简短明确的调用,关键字参数提高可读性。默认值在定义时求值,不会为每次调用重新创建。用空列表做默认参数容易让不同调用共享同一个列表;使用None作为哨兵并在函数内部创建容器。

局部变量通常只在当前函数作用域中可见。尽量通过返回值传递结果而不是修改全局变量;需要修改传入列表时,在函数名和约定中表达清楚。类型注解是说明与工具支持,不会自动执行运行时校验。

测试从需求出发:0分钟该怎样显示、负数是否拒绝。assert可用于练习中的内部检查,但生产输入校验应用明确异常或分支,因为优化执行可关闭断言。

可运行示例

使用Python 3.11或更新版本,将代码保存为独立练习目录中的lesson.py,在该目录执行 python3 lesson.py;Windows也可使用 py lesson.py。示例只使用标准库。

def duration_label(minutes):
    if type(minutes) is not int or minutes < 0:
        raise ValueError("minutes must be a nonnegative integer")
    hours, rest = divmod(minutes, 60)
    return f"{hours}h {rest}m"

def collect(title, items=None):
    if items is None:
        items = []
    items.append(title)
    return items

assert duration_label(125) == "2h 5m"
assert duration_label(0) == "0h 0m"
print(duration_label(125))
print(collect("Python"), collect("SQL"))

推演与验证

输出2h 5m,两个独立列表分别包含Python与SQL。把items默认值改为[]后多次调用,观察状态泄漏,再恢复。传入True会被当前严格整数规则拒绝,这一规则是有意的,因为bool在Python中属于int子类。

常见错误与边界

不要用捕获所有异常掩盖函数约定不清。默认参数和传入容器的所有权要写清楚:示例在调用者提供列表时会修改该列表。

动手练习

增加format_average(scores),空集合返回None,有数据时返回平均值。测试不修改原列表,并说明是否接受浮点分数。

参考解法与理由

把过滤或拒绝策略放在函数入口。返回数字由调用者决定展示精度,避免业务计算函数只能返回带装饰文字的字符串。

深入实验:用函数契约处理空数据与非法输入

学习时长平均值涉及三个决定:单位是什么、空列表返回什么、错误数据是否静默忽略。下面约定只接受非负整数分钟,空列表没有平均值,返回None;数据错误直接报错。这比把所有失败都返回0更容易区分“没有学习”与“没有数据”。

def average_minutes(values):
    if any(type(value) is not int or value < 0 for value in values):
        raise ValueError("minutes must be nonnegative integers")
    if not values:
        return None
    return sum(values) / len(values)

print(average_minutes([]))
print(average_minutes([0, 30, 60]))
for bad in [[-1], [True], ["30"]]:
    try:
        average_minutes(bad)
    except ValueError:
        print("rejected")
assert average_minutes([0]) == 0

预期观察

输出None、30.0,然后三行rejected。True虽然是Python整数的子类,在这里也被拒绝,因为它不是时长记录。

原因、取舍与适用边界

使用type(value) is int是有意的业务限制,不是所有代码都应照搬的规则。当前函数要求可重复遍历、可求len的列表式输入;若传生成器,any会消耗它而len又不可用,需要先转成受大小限制的列表或改为单次迭代。计算与显示分开,None可以在界面显示“暂无记录”,保留一个没有副作用的计算函数会更容易测试。

进阶练习

如果新增需求允许小数分钟,直接删除类型校验有什么隐患?

解法与推理

字符串、布尔值、NaN和无穷值也可能进入计算。应重新定义数值类型和有限性约束,继续拒绝负值,并给空输入、零、小数、NaN各加一个验收案例;单位仍必须统一。

进一步阅读