课程进度 课程大纲 已发布 24/24 课
Python 基础
数据与集合
构建可靠的程序
使用对象建模
专业 Python
高级 Python
把一份操作步骤变成按钮
一遍又一遍地复制相同的步骤,会让程序变得冗长而脆弱。函数会把一组步骤打包在一个名称之下。它就像一个按钮:只需定义一次这个按钮的功能,之后每当需要完成这项任务时,按下它即可。
- 定义 只编写一次操作步骤
- 调用 按下有名称的按钮
- 接收 使用返回的结果
你已经调用过 print()、input()、int() 和 range()。现在,你将定义自己的函数,并把第 2 章中的计算器重新构建为更小、更清晰的任务。
定义、调用和传递值
def 用来创建函数。函数中缩进的主体会等待,直到你调用它时才运行:
def greet_learner():
print("Ready to study Python!")
greet_learner()
greet_learner()
输出:
Ready to study Python!
Ready to study Python!
函数定义就是操作步骤;greet_learner() 是一次调用。实用的函数名会说明函数的用途,例如 calculate_total() 或 show_plan()。
形参是操作步骤中的局部标签。实参则是调用函数时传给它的值:
def describe_session(topic, minutes):
print(f"Study {topic} for {minutes} minutes.")
describe_session("functions", 35)
这里的 topic 和 minutes 是形参。"functions" 和 35 是实参。位置很重要:第一个实参会传给第一个形参。
-
实参到达
"functions", 35 -
形参为它们加上标签
topic, minutes - 函数主体开始工作 仅在被调用时运行
- 局部名称消失 这次调用结束之后
在函数内部赋值的名称通常是局部的。尝试在函数外部使用这样的名称会引发 NameError。请通过形参把所需的值传入函数,并使用 return 把有用的结果传出去。
return 不等于 print
print() 向人显示内容。return 把一个值交还给程序。这个区别非常重要:
def double_and_return(number):
return number * 2
result = double_and_return(15)
print(result + 5)
输出:
35
返回的整数可以参与其他计算。请对比下面这个只负责显示结果的函数:
def double_and_show(number):
print(number * 2)
result = double_and_show(15)
print(result)
输出:
30
None
第一行来自函数内部。因为函数运行到末尾时没有返回任何值,所以它的结果是 None。
- print() 向人显示一个值
- return 把一个值发送给调用者
-
没有 return
调用会产生
None
函数的简短约定会说明传入什么、进行什么处理,以及返回什么。清晰的名称加上一行文档字符串,可以明确表达这份约定:
def calculate_total_minutes(minutes_per_day, days):
"""Return the total minutes in a daily study plan."""
return minutes_per_day * days
当 Python 执行到 return 时,本次调用会立即结束。同一个代码块中,位于无条件 return 之后的代码无法运行。
用多个小任务构建计算器
优秀的小程序会把三项任务分开:输入负责收集值,处理负责返回结果,输出负责展示结果。请把下面这个仅使用标准库的完整项目保存为 study_time_functions.py:
def calculate_total_minutes(minutes_per_day, days):
"""Return the total minutes in a daily study plan."""
return minutes_per_day * days
def minutes_to_hours(total_minutes):
"""Return minutes expressed as hours."""
return total_minutes / 60
def show_plan(topic, minutes_per_day, days, total_minutes, total_hours):
"""Display one friendly study plan."""
print()
print(f"Topic: {topic}")
print(f"Daily practice: {minutes_per_day} minutes")
print(f"Days: {days}")
print(f"Plan: {total_minutes} minutes")
print(f"Total time: {total_hours:.1f} hours")
def main():
topic = input("Topic: ")
minutes_per_day = int(input("Minutes per day: "))
days = int(input("Number of days: "))
total_minutes = calculate_total_minutes(minutes_per_day, days)
total_hours = minutes_to_hours(total_minutes)
show_plan(topic, minutes_per_day, days, total_minutes, total_hours)
main()
运行它,并依次输入 Python、30 和 7。确认最后一部分如下所示:
Topic: Python
Daily practice: 30 minutes
Days: 7
Plan: 210 minutes
Total time: 3.5 hours
你也可以在不输入内容的情况下测试负责处理的函数:
print(calculate_total_minutes(45, 4))
print(minutes_to_hours(180))
这两行应该分别打印 180 和 3.0。这就是小型计算函数容易检查的原因。
三个小任务和清晰的约定
- 把天数转换为分钟。 编写
days_to_minutes(days, hours_per_day)并返回答案。days_to_minutes(3, 2)应该返回 360;请在函数外部打印结果。 - 目标判断助手。 编写
target_met(actual, target),让它返回一个布尔值。使用(29, 30)、(30, 30)和(31, 30)进行测试。 - 可重复使用的标签。 编写
session_label(topic, minutes),让它返回类似Python: 40 minutes的文本。保存它的结果,然后打印两次。
需要小心的地方:使用错误数量的实参调用函数会引发 TypeError。把文本传给 int() 会引发 ValueError;在乘法运算中,天数为零是安全的,但在需要除以天数来计算平均值时则不安全。后面的章节将介绍默认值、类型提示和友好的输入验证。
准备好处理文本了吗?
- 我可以定义一个函数,并在之后调用它。
- 我可以分辨形参和实参。
- 我可以按照预期顺序传入多个实参。
- 我知道
print()负责显示内容,而return负责把值传回去。 - 我知道为什么没有显式返回值的函数会产生
None。 - 我可以让临时名称保持在函数的局部作用域内。
- 我可以用一份简短的约定描述输入、处理过程和结果。
- 我重构后的计算器会得到 210 分钟和 3.5 小时。
- 我完成了三个小任务。
接下来,你将在检查、规范化、搜索、拆分和连接 Unicode 文本时重复使用这些小函数。