跳转到正文

第二章:函数

更新时间: 2026-09-08 19:18 · 中文版
暂无英文版
目录 (13)

章节导航上一章:第一章 基本数据类型与基础运算下一章:第三章 常用内置函数与函数调用

在数据处理与日常业务核算中,我们经常遇到重复出现的计算规则。例如,无论购买什么商品、买多少件,计算商品总价都遵循“单价 × 数量”;核算企业营业利润,都遵循“营业收入 - 营业成本”。

在初等数学中,我们用函数来描述这种对应规律:

y = f(x)

其中 x 是自变量(输入数据),f 是对应法则(处理规则),y 是因变量(计算结果)。如果输入有多个,例如经济学中的生产函数 Q = f(K, L),资本 K 和劳动 L 就是两个自变量,产出 Q 则是结果。

在计算机与信息处理中,这种结构通常抽象为一个系统(System)

\text{输入(Input)} \longrightarrow \text{处理器(Processor)} \longrightarrow \text{输出(Output)}

在 Python 中,**函数(Function)**就是这种系统思维的实现工具:

  • 参数(Parameters):对应系统的输入(自变量);
  • 函数体(Function Body):对应系统的处理器(计算逻辑与步骤);
  • 返回值(Return Value):对应系统的输出(计算结果)。

本章核心主线介绍函数的系统模型、定义与调用、参数与返回值、冒号与代码块机制,以及局部变量的作用域。扩展阅读介绍关键字参数与默认参数、多值返回、文档字符串和外部变量。


第一部分:核心主线

2.1 从重复计算到系统化封装

在编写程序时,如果相同的规则在多处重复手写,代码会变得冗长且难以维护:

案例:计算同一件单价 28.5 元的商品在购买 4 件与 3 件时的小计金额。

python
unit_price = 28.5
print(unit_price * 4)
print(unit_price * 3)

输出:

text
114.0
85.5

两次计算中,“单价 × 数量”这一计算规则完全相同,只有数量在变化。我们可以把这个处理规则封装为一个函数:让单价和数量作为输入,让函数完成乘法处理,并把计算结果作为输出交还给我们。

2.2 定义与调用函数

2.2.1 定义函数的基本语法

定义一个函数需要使用关键字 def(define 的缩写),其基本书写格式如下:

python
def 函数名(参数1, 参数2):
    处理语句1
    处理语句2
    return 结果

这里包含三个关键语法要素:

  1. 函数名:遵循第一章变量命名规则(小写字母加下划线,清晰表达功能);
  2. 圆括号与参数:括号内列出用于接收输入数据的参数名;即使函数不需要输入,圆括号也必须保留;
  3. 冒号 : 与代码块:行末必须写一个英文冒号,用于引出下方的函数体。

2.2.2 核心机制:代码块(Code Block)与缩进

在 Python 中,**代码块(Code Block)是指属于同一个逻辑结构的一组语句。Python 不像 C、Java 那样使用大括号 {} 来包裹代码,而是通过缩进(Indentation)**来界定代码块的归属:

  • 冒号引出代码块:在 def 行末写下冒号 :,意味着从下一行开始进入属于该函数的代码块;
  • 缩进规范:Python 官方 PEP 8 风格指南推荐统一使用 4 个空格 作为一个缩进层级。在 VS Code 中,状态栏右下角应确认设置为“空格: 4”,此时只需按一次键盘上的 Tab 键,编辑器就会自动输入 4 个空格;
  • 层级一致性:同一个代码块内的所有语句必须保持完全相同的缩进距离;
  • 退出代码块:当需要结束函数的定义时,只需将下一行代码恢复顶格书写,不再缩进。

这一机制是 Python 最核心的语法特征。不仅函数体是一个代码块,后续章节学习的条件分支(if 代码块)以及循环遍历(forwhile 代码块),都会采用这种“冒号 + 缩进”的统一结构。

案例:定义商品小计函数,计算 4 件单价 28.5 元商品的小计金额。

python
def calculate_subtotal(unit_price, quantity):
    subtotal = unit_price * quantity
    return subtotal

amount = calculate_subtotal(28.5, 4)
print(amount)

输出:

text
114.0

2.2.3 程序的执行流程

计算机在执行上述程序时,分为“定义”与“调用”两个阶段:

  1. 定义阶段(第 1–3 行)
    • 计算机遇到 def calculate_subtotal(...),在内存中登记这个函数名和它包含的处理规则;
    • 此时缩进的代码块并不会被执行,它只是被记录下来待命。
  2. 调用阶段(第 5 行)
    • 程序执行到 calculate_subtotal(28.5, 4),看到函数名后有圆括号,立即触发函数调用;
    • 系统的输入开始生效:实参 28.5 传递给形参 unit_price,实参 4 传递给形参 quantity
    • 计算机跳转到函数内部的代码块,执行处理器逻辑:计算 28.5 * 4,并将结果赋给内部变量 subtotal
    • 遇到 return subtotal,处理完成,将计算结果 114.0 作为输出交还给调用处;
    • 外面的变量 amount 接收到输出值 114.0
  3. 后续执行(第 6 行)
    • 执行 print(amount),将结果显示到终端。

2.2.4 缩进错误排查

如果在编写函数体时没有缩进,或者同一块代码缩进空格数不一致,Python 解释器在编译时就会直接报错(IndentationError):

案例:观察折扣计算函数中缩进代码块与外层调用语句的区分。

python
def apply_discount(price, rate):
    discounted_price = price * rate
    return discounted_price

final_price = apply_discount(120.0, 0.85)
print(final_price)

输出:

text
102.0

试一试(必做):故意删除 discounted_price = ... 前面的 4 个空格并运行,阅读终端中提示的 IndentationError: expected an indented block after function definition。恢复缩进后再次运行,核对结果。这种单点改错练习可以帮助你快速熟悉缩进报错的特征。

2.3 参数:向函数传入数据

定义时的参数名可以看作待接收数据的名称;调用时提供的是本次使用的实际数据,也称实参。每次调用可以提供不同的数据。

2.3.1 一个参数

案例:把视频时长从分钟换算为秒,分别计算 5 分钟和 12 分钟对应的秒数。

python
def minutes_to_seconds(minutes):
    return minutes * 60

print(minutes_to_seconds(5))
print(minutes_to_seconds(12))

输出:

text
300
720

2.3.2 多个位置参数

多个参数用逗号隔开。按位置传参时,数据与参数从左到右依次对应。

案例:将“小时和分钟”形式的行程时间换算为总分钟数。

python
def to_total_minutes(hours, minutes):
    return hours * 60 + minutes

travel_minutes = to_total_minutes(1, 30)
print(travel_minutes)

输出:

text
90

这里 hours 接收 1minutes 接收 30。交换两个实参的位置,会变成“30 小时 1 分钟”,即 1801 分钟。

试一试(必做):先预测 to_total_minutes(2, 15) 的结果,再运行核对。也可以尝试少传一个参数,阅读错误信息中指出的参数名。

2.3.3 传入并返回字符串

参数与返回值可以是字符串、数字或其他类型的数据,具体由函数要完成的任务决定。

案例:去掉姓名两端的空格,并生成报名确认文字。

python
def make_confirmation(raw_name):
    name = raw_name.strip()
    return f"{name},报名已确认。"

message = make_confirmation("  李明辉  ")
print(message)

输出:

text
李明辉,报名已确认。

2.4 返回值与屏幕输出

2.4.1 return 与 print

return 决定本次函数调用得到什么值。print() 把内容写到输出位置,通常显示在终端;它本身返回 None。一个函数运行到结尾,若没有返回表达式,也会返回 None,表示这里没有提供具体的计算结果。

案例:分别调用“返回求和结果”和“打印求和结果”的函数,观察哪个调用结果能够继续参与运算。

python
def add_with_return(a, b):
    return a + b


def add_with_print(a, b):
    print(a + b)


total_1 = add_with_return(100, 200)
print(total_1 + 6)

total_2 = add_with_print(100, 200)
print(total_2)

# 取消下一行的注释会引发 TypeError:None 不能与整数相加。
# print(total_2 + 6)

输出:

text
306
300
None

第一行来自求和结果加 6。第二行是 add_with_print() 在函数内部打印的内容。第三行是这个函数的返回值。若调用者需要继续使用和,就应返回这个和;只用于显示通知的函数可以不返回具体结果。

单独写 return、不在后面跟表达式,也会返回 None。None 的更多用法见第一章补充部分Python 函数说明

2.4.2 return 后面的执行顺序

案例:计算商品小计,返回结果后,观察同一函数体中后面的输出语句是否执行。

python
def calculate_subtotal(unit_price, quantity):
    subtotal = unit_price * quantity
    return subtotal
    print("已经执行到这一行")

amount = calculate_subtotal(28.5, 4)
print(amount)

输出:

text
114.0

本例执行到 return 时就返回调用处,因此后面的 print() 不会执行。观察完成后,可以删除这条不会执行的语句。

试一试(必做):把这条 print() 移到 return 前面,先预测输出顺序,再运行核对。

2.5 局部变量

2.5.1 函数内部的名称

本节函数中的参数和赋值创建的名称是局部变量。函数外部通过返回值取得结果,不能直接使用函数内部的局部名称。名称的作用域

案例:计算一笔活动的收入减成本,用返回值在函数外继续显示结果。

python
def calculate_net_income(income, cost):
    net = income - cost
    return net

income_result = calculate_net_income(10000, 3500)
print(income_result)

# 取消下一行的注释会引发 NameError:外面没有定义 net。
# print(net)

输出:

text
6500

net 是函数内部的名称,income_result 是外部接收返回值的名称。函数返回后,这个结果仍可以被后面的程序使用。

2.5.2 同名局部变量

不同函数可以使用相同的局部变量名,这些名称分别属于各自的函数。

案例:分别按给定规则计算配送费和打印费,两个函数都用 fee 保存本次计算结果。

python
def delivery_charge(distance):
    fee = distance * 2.0  # 本例按每公里 2 元计算
    return fee


def printing_charge(pages):
    fee = pages * 0.5  # 本例按每页 0.5 元计算
    return fee


print(delivery_charge(5))
print(printing_charge(6))

输出:

text
10.0
3.0

主线练习(必做)

练习 1:外卖账单结算函数

任务:编写函数 calculate_takeout_bill(food_total, coupon_discount, delivery_fee),根据餐品总额、优惠券金额以及配送费,计算并返回应付金额。

业务规则

  • 应付金额 = 餐品总额 - 优惠券金额 + 配送费;
  • 题目保证优惠券金额不超过餐品总额,各项金额均为非负数。

编写完成后,用 print() 打印函数调用结果,并使用下表数据自检:

测试调用 期望返回值 说明
calculate_takeout_bill(80, 10, 6) 76 正常使用 10 元优惠券与 6 元配送费
calculate_takeout_bill(80, 0, 6) 86 无优惠券
calculate_takeout_bill(80, 10, 0) 70 免配送费

练习 2:阶梯快递运费计算函数

任务:编写函数 calculate_freight(weight),按照给定的重量计算快递运费并返回。

计费规则

  • 寄送包裹的计费重量 weight 为大于 1 的整数(单位:公斤);
  • 首重 1 公斤收费 12 元;
  • 超出 1 公斤的续重部分,每公斤加收 5 元(续重公斤数为 weight - 1);
  • 运费计算公式:12 + (weight - 1) * 5

编写完成后,核对以下测试调用的返回值:

测试调用 期望返回值 计算过程
calculate_freight(2) 17 首重 12 元 + 续重 1 公斤 × 5 元 = 17 元
calculate_freight(4) 27 首重 12 元 + 续重 3 公斤 × 5 元 = 27 元

练习 3:电影票总额核算函数

任务:编写函数 calculate_ticket_fare(ticket_count, discount_rate),根据购票张数与已确定的折扣率,计算并返回实付总价。

业务规则

  • 单张电影票的原价为 45 元;
  • ticket_count 为正整数购票张数;
  • discount_rate 为已确定的折扣比例,例如 1.0 表示原价无折扣,0.8 表示八折优惠;
  • 应付总额计算公式:ticket_count * 45 * discount_rate

编写完成后,核对以下测试调用的返回值:

测试调用 期望返回值 说明
calculate_ticket_fare(9, 1.0) 405.0 购买 9 张原价票
calculate_ticket_fare(10, 0.8) 360.0 购买 10 张团体票,享受八折

提示:根据购票张数由程序自动判断并选择折扣率的逻辑,将在下一章条件分支(if/else)中学习。当前阶段由外部调用者直接提供折扣率。


第二部分:扩展阅读

以下内容供学有余力或需要进一步提升代码灵活度的读者选读。完成核心主线练习不需要依赖本部分内容。

2.6 提高调用灵活性:关键字参数与默认参数

2.6.1 按参数名传入数据(关键字参数)

在调用函数时,除了按照形参的位置依次传参(位置参数),还可以写出 形参名 = 数据,这种调用方式称为关键字参数(Keyword Arguments)

使用关键字参数时,实参的传递顺序可以任意调整,Python 会根据参数名自动对号入座:

案例:计算 1 小时 30 分钟对应的总分钟数,使用关键字参数明确各个输入的含义,并调换传参顺序。

python
def to_total_minutes(hours, minutes):
    return hours * 60 + minutes

# 1. 传统位置参数调用
print(to_total_minutes(1, 30))

# 2. 关键字参数调用(顺序调换,结果依然正确)
print(to_total_minutes(minutes=30, hours=1))

输出:

text
90
90

当函数参数较多时,使用关键字参数能让调用处的代码含义非常明确,降低传错顺序的风险。

2.6.2 为参数设置默认值(默认参数)

在定义函数时,可以为某些参数预先指定一个默认值。如果在调用时没有提供该参数,函数就自动采用默认值;如果提供了新值,则覆盖默认值。

语法规则:在函数定义中,有默认值的参数必须放置在没有默认值的参数后面,否则会引发 SyntaxError: non-default argument follows default argument

案例:计算订单结算金额。普通订单默认收取 6 元运费,如果是免运费订单,调用时可传入 0.0 覆盖默认运费。

python
def checkout(subtotal, shipping_fee=6.0):
    return subtotal + shipping_fee

# 未传入 shipping_fee,自动使用默认值 6.0
print(checkout(100.0))

# 传入 shipping_fee=0.0,覆盖默认值
print(checkout(100.0, shipping_fee=0.0))

输出:

text
106.0
100.0

案例:使用默认参数改写主线中的电影票总额函数,默认折扣率为 1.0(原价)。

python
def calculate_ticket_fare_with_default(ticket_count, discount_rate=1.0):
    return ticket_count * 45 * discount_rate

# 普通购票无需输入折扣率
print(calculate_ticket_fare_with_default(9))

# 团体购票显式指定八折优惠
print(calculate_ticket_fare_with_default(10, discount_rate=0.8))

输出:

text
405.0
360.0

2.7 一次返回多个结果:多值返回与解包

在很多业务场景中,一个处理过程会产生多个关联结果。在 return 语句后用英文逗号分隔多个表达式,函数就会将它们打包成一个**元组(tuple)**一并返回。

调用者可以用单个变量接收这个元组,也可以用相同数量的变量分别接收其中的每一个值,后者称为解包(Unpacking)

案例:将总分钟数换算为“整小时数”和“剩余分钟数”,观察元组打包与变量解包的两种接收方式。

python
def split_duration(total_minutes):
    hours = total_minutes // 60
    minutes = total_minutes % 60
    return hours, minutes

# 方式 1:用单个变量接收完整的元组
duration_tuple = split_duration(95)
print(duration_tuple)

# 方式 2:用两个变量分别解包接收对应的值
h, m = split_duration(95)
print(f"{h} 小时 {m} 分钟")

输出:

text
(1, 35)
1 小时 35 分钟

解包接收时,左侧变量的顺序必须与函数内部 return 返回值的顺序完全一致。关于元组的详细性质与高级操作,将在后续容器章节深入学习。

2.8 函数的说明文档:文档字符串(docstring)

在实际项目中,规范的代码应当附带使用说明。写在函数体第一行的字符串称为文档字符串(docstring),通常使用三引号 """ 书写。

文档字符串用于向其他阅读代码的人(以及未来的自己)说明函数的功能、参数含义和返回值格式:

案例:给商品小计函数编写规范的文档字符串,并使用内置函数 help() 查看该说明。

python
def calculate_subtotal(unit_price, quantity):
    """
    根据商品单价与购买数量,计算并返回商品小计金额。

    参数:
        unit_price (float): 商品单价(元)
        quantity (int): 购买数量(件)

    返回:
        float: 商品小计金额(元)
    """
    return unit_price * quantity

# 正常调用函数
print(calculate_subtotal(28.5, 4))

# 查看函数的说明文档
help(calculate_subtotal)

输出:

text
114.0
Help on function calculate_subtotal in module __main__:

calculate_subtotal(unit_price, quantity)
    根据商品单价与购买数量,计算并返回商品小计金额。
    
    参数:
        unit_price (float): 商品单价(元)
        quantity (int): 购买数量(件)
    
    返回:
        float: 商品小计金额(元)

在 VS Code 中,只要为函数编写了文档字符串,当你在其他地方输入该函数名或将鼠标悬停在函数名上方时,编辑器就会弹出格式整齐的说明浮窗。

2.9 全局变量与外部作用域

定义在函数外部、属于整个代码文件最顶层的变量称为全局变量(Global Variable)

函数内部可以直接读取外部全局变量的值;如果需要在函数内部修改全局变量的值,则必须使用 global 关键字进行特殊声明。

案例:对比“依赖外部全局变量”与“通过参数显式传递”两种编程风格。

python
# 推荐风格:通过参数显式传入数据,通过返回值交回结果
def checkout_clean(subtotal, fee):
    return subtotal + fee

shipping_fee = 6.0
final_bill = checkout_clean(100.0, shipping_fee)
print(final_bill)

输出:

text
106.0

设计建议: 在编写函数时,应当尽量通过参数将外部数据输入函数,通过返回值将计算结果输出。避免让函数隐式读取或修改外部全局变量。明确的输入与输出能让函数像独立的积木一样,在不同程序中安全、方便地重复使用。


扩展练习(选做)

练习 1:定期存款本息计算(多值返回)

任务:编写函数 calculate_deposit_return(principal, annual_rate, years),按单利规则同时计算并返回利息以及到期本息合计。需要 §2.7 多值返回与解包知识。

计算规则

  • 利息 = 本金 × 年利率 × 存期年数;
  • 本息合计 = 本金 + 利息;
  • 函数同时返回 (利息, 本息合计)

编写完成后,在函数外部用两个变量分别解包接收返回值,并用下表数据核对:

测试调用 期望返回元组 解包打印结果
calculate_deposit_return(10000, 0.02, 1) (200.0, 10200.0) 利息: 200.0 元,本息合计: 10200.0 元
calculate_deposit_return(10000, 0.02, 3) (600.0, 10600.0) 利息: 600.0 元,本息合计: 10600.0 元

练习 2:为运费函数编写规范文档字符串

任务:为你编写的 calculate_freight(weight) 函数添加规范的文档字符串。需要 §2.8 文档字符串知识。

要求在三引号说明中包含:

  1. 函数的业务功能简述;
  2. 输入参数 weight 的含义、单位与“大于 1 的整数”这一取值限制;
  3. 返回值的含义与单位。

编写完成后,使用 help(calculate_freight) 在终端输出说明文档,确认文字格式整齐。


章节导航上一章:第一章 基本数据类型与基础运算下一章:第三章 常用内置函数与函数调用