JEPA4Japan · 教程

函数:把步骤变成可复用的工具

1,662字 5分钟阅读 #Python

使用参数、返回值和清晰的约定,把重复工作封装成函数。

课程进度 课程大纲 已发布 24/24 课

把一份操作步骤变成按钮

一遍又一遍地复制相同的步骤,会让程序变得冗长而脆弱。函数会把一组步骤打包在一个名称之下。它就像一个按钮:只需定义一次这个按钮的功能,之后每当需要完成这项任务时,按下它即可。

  1. 定义 只编写一次操作步骤
  2. 调用 按下有名称的按钮
  3. 接收 使用返回的结果
定义一次任务,多次调用它,并重复使用它的结果。

你已经调用过 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 是实参。位置很重要:第一个实参会传给第一个形参。

  1. 实参到达 "functions", 35
  2. 形参为它们加上标签 topic, minutes
  3. 函数主体开始工作 仅在被调用时运行
  4. 局部名称消失 这次调用结束之后
每次调用都有自己临时使用的形参名称。

在函数内部赋值的名称通常是局部的。尝试在函数外部使用这样的名称会引发 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。

  1. print() 向人显示一个值
  2. return 把一个值发送给调用者
  3. 没有 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。这就是小型计算函数容易检查的原因。

三个小任务和清晰的约定

  1. 把天数转换为分钟。 编写 days_to_minutes(days, hours_per_day) 并返回答案。days_to_minutes(3, 2) 应该返回 360;请在函数外部打印结果。
  2. 目标判断助手。 编写 target_met(actual, target),让它返回一个布尔值。使用 (29, 30)、(30, 30) 和 (31, 30) 进行测试。
  3. 可重复使用的标签。 编写 session_label(topic, minutes),让它返回类似 Python: 40 minutes 的文本。保存它的结果,然后打印两次。

需要小心的地方:使用错误数量的实参调用函数会引发 TypeError。把文本传给 int() 会引发 ValueError;在乘法运算中,天数为零是安全的,但在需要除以天数来计算平均值时则不安全。后面的章节将介绍默认值、类型提示和友好的输入验证。

准备好处理文本了吗?

  • 我可以定义一个函数,并在之后调用它。
  • 我可以分辨形参和实参。
  • 我可以按照预期顺序传入多个实参。
  • 我知道 print() 负责显示内容,而 return 负责把值传回去。
  • 我知道为什么没有显式返回值的函数会产生 None。
  • 我可以让临时名称保持在函数的局部作用域内。
  • 我可以用一份简短的约定描述输入、处理过程和结果。
  • 我重构后的计算器会得到 210 分钟和 3.5 小时。
  • 我完成了三个小任务。

接下来,你将在检查、规范化、搜索、拆分和连接 Unicode 文本时重复使用这些小函数。