跳转到正文

第二章:函数

更新时间: 2026-10-10 13:30 · 中文版
目录 (13)

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

在数据处理与商业计算中,相同规则的重复计算十分普遍。例如,核算商品小计遵循“单价 × 数量”;核算企业营业利润遵循“营业收入 - 营业成本”。

在数学中,函数用于描述自变量与因变量之间的确定性对应关系:

y = f(x)

其中 x 为自变量(输入数据),f 为对应法则(映射与计算规则),y 为因变量(计算结果)。若输入包含多个自变量,例如经济学中的生产函数 Q = f(K, L),资本投入 K 与劳动投入 L 为输入变量,总产出 Q 为计算结果。

在计算机科学中,这种结构被抽象为输入-处理-输出系统(Input-Process-Output System, IPO):

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

在 Python 中,函数(Function) 是这一系统思维的直接实现工具:

  • 形参(Parameters):对应系统的输入(自变量),在函数入口处接收外部传入的数据;
  • 函数体(Function Body):对应系统的处理环节,封装具体的计算逻辑与操作步骤;
  • 返回值(Return Value):对应系统的输出(因变量),将计算结果交付给外部调用方。

本章分为两大部分:第一部分:核心内容讲解函数的定义与调用流程、代码块与缩进机制、参数传递、返回值与终端输出的区别、局部变量的作用域,以及显式输入输出类型标注规范;第二部分:扩展知识点介绍关键字参数与默认参数、多值返回与解包,以及文档字符串规范。


第一部分:核心内容

2.1 为什么需要函数

在第一章 1.2.4 节中,我们通过变量 shipping_fee 集中管理了公共运费。当运费数值变动时,只需修改一处变量赋值即可。变量管理的是可变的数据数值。

但在实际开发中,需要被反复使用的往往不仅是简单数值,也包含处理逻辑与计算规则。当多笔业务需要执行相同的处理逻辑与计算步骤时,就需要定义函数把反复使用的处理逻辑封装成一个整体,方便复用。

案例:网店初期按“单价 × 数量 + 运费”核算三笔订单的实付金额:

python
shipping_fee = 6.0

# 订单 1:笔记本(单价 50.0 元,2 件)
total_1 = 50.0 * 2 + shipping_fee
print("订单 1 实付:", total_1)

# 订单 2:键盘(单价 200.0 元,1 件)
total_2 = 200.0 * 1 + shipping_fee
print("订单 2 实付:", total_2)

# 订单 3:显示器(单价 150.0 元,4 件)
total_3 = 150.0 * 4 + shipping_fee
print("订单 3 实付:", total_3)

输出:

text
订单 1 实付: 106.0
订单 2 实付: 206.0
订单 3 实付: 606.0

在这段代码中,三笔订单输入的数据不同(单价与数量),但都在重复执行完全相同的处理逻辑(单价 × 数量 + 运费)。

复杂处理逻辑带来的代码冗余与维护困境

假设促销规则调整,订单结算包含多步计算流程:

  1. 核算小计:\text{subtotal} = \text{单价} \times \text{数量};
  2. 享受店铺 8 折优惠:\text{discounted} = \text{subtotal} \times 0.8;
  3. 加收 10% 增值税:\text{tax} = \text{discounted} \times 0.1;
  4. 计算最终实付:\text{total} = \text{discounted} + \text{tax} + \text{运费}。

在没有函数封装的情况下,代码将陷入“逻辑重复书写”与“维护困难”的双重困境。我们必须在每一笔订单处,把原来的单行表达式替换为这 4 步计算:

python
# 订单 1 的结算逻辑(4 步流程)
subtotal_1 = 50.0 * 2
discounted_1 = subtotal_1 * 0.8
tax_1 = discounted_1 * 0.1
total_1 = discounted_1 + tax_1 + shipping_fee
print("订单 1 实付:", total_1)

# 订单 2 的结算逻辑(完全重复的 4 步)
subtotal_2 = 200.0 * 1
discounted_2 = subtotal_2 * 0.8
tax_2 = discounted_2 * 0.1
total_2 = discounted_2 + tax_2 + shipping_fee
print("订单 2 实付:", total_2)

# 订单 3 同样需要重复书写上述 4 步...

直接复制粘贴这套处理逻辑,会引发两个根本性问题:

  • 重复书写相同的处理逻辑(代码冗余): 结算规则是一套通用的处理步骤。如果系统有 100 笔订单,这 4 步相同的逻辑就要机械地重复书写 100 次,导致代码急剧膨胀,且充斥大量无意义的重复代码。
  • 修改点分散导致维护困难(维护成本高且极易出错): 由于处理逻辑散落在每个订单的计算流程中,若未来业务规则再次调整(如优惠规则改变),开发者必须在代码中逐一找出所有编写过这套结算逻辑的地方逐一修改。一旦漏改或改错某一处,不同订单就会采用不一致的结算标准,产生隐蔽的业务逻辑错误。

解决方案:通过函数封装处理逻辑

面对需要重复使用的处理逻辑,最佳方案是将其 封装(Encapsulate) 为一个独立的功能单元,即函数:

python
# 封装通用的订单结算流程
# 输入:商品单价(unit_price)、购买数量(quantity)
# 输出:订单实付金额(返回值)
# 机制:不同的输入数据,经由同一个处理流程,得到各自对应的期望结果
def calculate_order_total(unit_price, quantity):
    shipping_fee = 6.0
    subtotal = unit_price * quantity        # 步骤 1:核算商品小计
    discounted = subtotal * 0.8             # 步骤 2:计算 8 折优惠价
    tax = discounted * 0.1                  # 步骤 3:加收 10% 增值税
    return discounted + tax + shipping_fee  # 步骤 4:计算最终实付金额并返回结果


# 调用函数:传入各订单商品价格和购买数量,使用变量 subtotal 捕获返回值并在输出时使用

# 订单 1:笔记本。传入参数(单价 50.0 元,2 件)
subtotal = calculate_order_total(50.0, 2)
print("订单 1 实付:", subtotal)

# 订单 2:键盘。传入参数(单价 200.0 元,1 件),再次使用 subtotal 变量捕获新订单结果
# 注意:订单 1 的计算结果已被覆盖
subtotal = calculate_order_total(200.0, 1)
print("订单 2 实付:", subtotal)

# 订单 3:显示器。传入参数(单价 150.0 元,4 件),继续使用 subtotal 变量捕获新订单结果
# 注意:订单 2 的计算结果已被覆盖
subtotal = calculate_order_total(150.0, 4)
print("订单 3 实付:", subtotal)

输出:

text
订单 1 实付: 94.0
订单 2 实付: 182.0
订单 3 实付: 534.0

通过函数封装,我们把通用的处理流程打包为一个整体,同时达成了流程复用与便捷维护两个核心目标:

  • 封装通用计算步骤,实现处理流程复用(Process Reuse): 基于本章开头输入-处理单元-输出的系统模型,定义一个函数本质上就是构建一个相对独立的小系统:
    1. 首先明确输入是什么(前面例子中def行括号里需要的参数unit_price 和 quantity);
    2. 其次明确输出是什么(通过 return 返回的内容,例如上例中的订单实付总额);
    3. 函数体则定义了如何一步一步处理输入并最终产出输出(例如核算小计 \to 计算折扣 \to 加收税费 \to 汇总运费)。
    4. 这套通用的处理流程只需在函数体内定义一次。面对任意多笔订单,外部只需调用函数并给定相应的输入,函数便会按预设步骤处理输入并返回期望结果。调用方无需关心内部具体的运算细节。
  • 集中管理处理流程,实现便捷维护(Centralized Maintainability): 函数封装将原本散落在各个订单中的处理流程集中收拢到唯一的函数体内,构建了程序的单一维护入口(Single Source of Truth,单一真实信源)。当业务结算规则发生变动时(例如店铺折扣从 8 折调整为 85 折,或免除增值税),开发者只需在函数定义内部修改这一处代码,所有调用该函数的地方都会自动同步应用新的计算流程,程序的可维护性显著提升。

2.2 定义与调用函数

2.2.1 定义函数的基本语法

在 Python 中,使用关键字 def(define 的缩写)定义函数,基本语法结构如下:

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

该结构包含五个语法要素:

  1. def 关键字:声明一个函数;
  2. 函数名:遵循第一章变量命名规则,采用下划线命名法(snake_case),名称清晰表达其功能;
  3. 圆括号与形参列表:圆括号内列出输入(input)的变量名,称为 形参(parameters);即使函数不需要输入,圆括号 () 也必须保留;
  4. 英文冒号 ::位于函数声明行末尾,开启函数体;
  5. 函数体与 return 语句:缩进部分为函数体,return 用于结束函数执行并返回计算结果(一个函数可以没有 return 语句,即不写 return)。

2.2.2 代码块(Code Block)与缩进

在 Python 中,代码块(Code Block) 指属于同一个逻辑结构的一组语句。许多编程语言(如 C、Java)使用大括号 {} 界定代码块的范围;Python 则采用 缩进(Indentation):

  • 冒号引出代码块:在 def 行末写下英文冒号 :,表示从下一行开始进入该函数的代码块;
  • 缩进规范:Python PEP 8 风格指南推荐统一使用 4 个空格 作为一个缩进层级。在 VS Code 中,按一次 Tab 键默认插入 4 个标准空格;
  • 层级一致性:同一个层级的所有语句,必须保持相同的缩进空格数;
  • 退出代码块:函数内部逻辑书写完毕后,后续语句恢复上一级缩进,即表示脱离该函数的作用范围。

不仅函数体由代码块构成,后续章节的条件分支(if)与循环结构(for、while),均采用这种“冒号 + 缩进”的统一机制。

案例:定义商品小计函数,计算 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 行):
    • 执行到 amount = calculate_subtotal(28.5, 4),通过圆括号触发函数调用:
      • 传递实参:将数据 28.5 与 4 分别传给形参 unit_price 和 quantity;
      • 执行计算:运行函数体内部语句完成计算;
      • 返回结果:通过 return 将结果 114.0 返回给调用处,并赋值给变量 amount。
  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
IndentationError: expected an indented block after function definition

练习:修复上面代码的缩进,然后删除 discounted_price = ... 前面的 4 个空格并运行,观察终端输出。恢复缩进后再次运行。通过主动排错练习,便于日后排查缩进错误。

2.3 参数:向函数传递数据

定义函数时括号内声明的变量名为形参(形式参数,Parameter),用于表示需要提供的输入;调用函数时传入的具体数值称为实参(实际参数,Argument)。通过在调用时传入不同的实参,同一函数即可完成不同数值的运算。

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 多个参数

当函数需要多项输入时,形参之间用英文逗号分隔。

按位置传参(Positional Arguments)是 Python 最基础的传参方式:解释器按照从左到右的位置顺序,将第一个实参绑定给第一个形参,第二个实参绑定给第二个形参。

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

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

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

输出:

text
90

本次调用中,形参 hours 接收 1,minutes 接收 30。若调换实参顺序写成 to_total_minutes(30, 1),含义变为“30 小时 1 分钟”,返回 1801 分钟。因此,使用位置参数时需确保参数顺序正确。

此外,实参数量必须与形参数量一致。少传参数会抛出 TypeError: to_total_minutes() missing 1 required positional argument: 'minutes';多传参数同样会报错。

练习:

  1. 先推算 to_total_minutes(2, 15) 的计算结果,再运行核对。尝试调用 to_total_minutes(2),观察终端提示的缺失参数信息。
  2. 编写一个函数 calculate_course_grade(attendance, pop_quiz, final_exam),接收平时考勤、随堂测验和期末考试三项成绩(满分均为 100 分),按照权重分别占 10%、30%、60% 计算并返回课程综合总评成绩(例如传入 90, 80, 85 应返回 84.0)。

练习 2 参考答案:

python
def calculate_course_grade(attendance, pop_quiz, final_exam):
    # 成绩权重构成:平时考勤 10%,随堂测验 30%,期末考试 60%
    total_grade = attendance * 0.1 + pop_quiz * 0.3 + final_exam * 0.6
    return total_grade

# 测试调用:平时考勤 90 分、随堂测验 80 分、期末考试 85 分
final_score = calculate_course_grade(90, 80, 85)
print(final_score)

输出:

text
84.0

2.3.3 传入与返回不同数据类型

函数的形参与返回值不仅可以是数值,也可以是字符串(str)、布尔值(bool)或后续将学习的列表、字典等任意合法数据类型。

案例:在电商订单与短信通知场景中,将 11 位手机号码的中间 4 位替换为星号 **** 做隐私脱敏保护。

python
def mask_phone_number(phone_number):
    # 提取前 3 位与后 4 位,中间拼接 4 个星号进行隐私脱敏
    masked = f"{phone_number[:3]}****{phone_number[7:]}"
    return masked

masked_phone = mask_phone_number("13812345678")
print(masked_phone)

输出:

text
138****5678

2.4 返回值与屏幕输出

2.4.1 return 与 print 的本质区别

初学编程时容易混淆 return 语句与 print() 函数。二者在程序中承担不同的职责:

  • print() 展示内容:将数据格式化为文本输出到终端屏幕,供人阅读。屏幕上显示的内容在后续代码中无法使用,且 print() 本身的返回值始终是 None;
  • return 返回结果:将函数内部的计算结果交还给函数调用处,使外部程序能够获取函数计算结果,以便在后续程序中继续使用。

直观理解:print() 相当于将想要展示的内容打印在纸上;return 相当于将加工好的零件递交给下一道工序。

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

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


def add_with_print(a, b):
    print(f'Print inside the function add_with_print: {a + b}')


total_1 = add_with_return(100, 200)
print(f'Return from the function add_with_return: {total_1}')
print(total_1 + 6)  # 直接打印,没有返回具体的值

total_2 = add_with_print(100, 200)  # total_2 接收的是 add_with_print 的返回值,即 None,可以理解为没有返回值
print(f'Return from the function add_with_print: {total_2}')
print(total_2 + 6)  # 报错 TypeError:None 不能与整数相加

输出:

text
Return from the function add_with_return: 300
306
Print inside the function add_with_print: 300
Return from the function add_with_print: None
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

输出结果解析:

  • 第一行 Return from the function add_with_return: 300:调用 add_with_return(100, 200) 时,函数通过 return 将计算结果 300 交付给外部变量 total_1,随后外部打印该变量;
  • 第二行 306:由于 total_1 成功接收到了具体数值 300,因此可以继续参与后续计算(total_1 + 6 得到 306);
  • 第三行 Print inside the function add_with_print: 300:执行 total_2 = add_with_print(100, 200) 时触发函数调用,执行到函数体内部的 print() 直接向终端屏幕输出文本;
  • 第四行 Return from the function add_with_print: None:因为 add_with_print() 内部没有 return 语句,函数执行完毕后默认返回 None,并赋值给外部变量 total_2;
  • 第五行 TypeError...:尝试执行 total_2 + 6 时,由于 total_2 的值为 None,无法与整数相加,解释器抛出类型错误(TypeError)。

如果函数执行到末尾没有书写 return,或者只写了单独的 return 而未在其后添加表达式,Python 默认返回 None。关于 None 的更多特性,可参阅第一章 1.7 小节。官方技术细节可参阅 Python 函数定义文档。

2.4.2 return 语句终止执行

return 语句在交还计算结果的同时,会立即终止当前函数的执行。解释器执行到 return 后直接返回调用处,函数体内位于 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 subtotal 时已退出,其后的 print("已经执行到这一行") 不会被触发(这类代码称为“不可达代码”)。

练习:将 print() 语句调整到 return 语句的前一行,预测终端的输出内容并运行验证。

2.5 局部变量与作用域

2.5.1 函数内部的名称与局部作用域

函数的输入参数(变量),以及函数体内创建的变量,统称为局部变量(Local Variable)。

局部变量只能在它的函数体内部使用,这一范围称为局部作用范围:

  1. 生命周期:局部变量在函数被调用时创建;函数执行完毕返回时,局部变量就会被清除(后续程序不能再使用该局部变量);
  2. 访问隔离:函数外部无法访问函数内部的局部变量。外部只能获取函数通过 return 返回的值。

案例:计算某项业务活动的净收益(收入减成本),通过返回值在外部接收结果。

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

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

# 从函数体外尝试读取函数内部的局部变量 net
# print(net)
# 取消上一行的注释会引发 NameError:外部作用域没有定义 net

输出:

text
6500

在此例中,net 是 calculate_net_income 函数内的局部变量,函数外部无法直接访问;net_income 是函数外部接收返回值的变量。函数执行完毕后,局部变量 net 已被释放(从内存里删除),但其计算结果 6500 已通过 return 语句返回并交由外部的 net_income 保存。

2.5.2 局部变量的命名空间独立性

不同函数拥有各自独立的局部命名空间(想象成函数自己的一块地盘)。不同的函数可以使用相同的局部变量名,彼此独立存储,互不影响。

案例:分别计算配送费与打印费,两个函数内部均使用局部变量名 fee 保存计算结果。

python
def delivery_charge(distance):
    fee = distance * 2.0  # 每公里 2.0 元
    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

Python 局部变量的命名空间独立性示意图

2.6 显式标注输入和输出类型

Python 是一门动态类型语言,定义函数时默认不需要声明变量类型。然而,当程序规模扩大或团队协作开发时,仅凭形参名(如 price、status)往往难以立刻确定应当传入哪种类型的数据。

为此,Python 提供了 类型注解(Type Annotations / 类型提示 Type Hints) 机制,允许开发者在定义函数时显式标明输入参数和返回值的期望类型:

  • 形参类型标注:在形参名后添加英文冒号与类型名称,例如 unit_price: float、quantity: int;
  • 返回值类型标注:在形参列表圆括号之后、行末冒号之前,使用箭头 -> 指明返回类型,例如 -> float。

案例:为商品小计计算函数显式标注输入形参与返回值的类型。

python
def calculate_subtotal(unit_price: float, quantity: int) -> float:
    return unit_price * quantity


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

输出:

text
114.0

核心特性说明:

  1. 非运行时强制约束:Python 解释器在运行程序时不会强行检查类型,也不会因传入非声明类型而中断报错。类型提示在本质上是代码级“说明书”;
  2. 提升开发体验与代码健壮性:在 VS Code 等主流编辑器中,类型提示能提供更精准的鼠标悬浮提示、自动代码补全,并能配合静态代码检查工具在程序运行前提前发现潜在的类型传递错误。

课程规范要求(本门课程必须遵守):显式类型标注是现代 Python 工程化的通用规范。本门课程后续所有代码示例、作业、测验与实战项目,在编写函数时均必须显式标注形参类型与返回值类型,以此养成严谨、规范的工程化编码习惯。


核心内容练习

练习 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 元

提示:对于包裹重量不到 1 公斤(或仅收取首重)等需要分情况判断的计费逻辑,后续将在条件分支(if/else)章节中讲解。当前阶段假设计费重量均为大于 1 的整数。

练习 3:用户登录验证函数

任务:编写函数 login(username, password),校验用户输入的用户名和密码是否正确,并返回布尔值(True 或 False)。

业务规则:

  • 设系统预设的正确账号为 "admin",预设密码为 "python2026";
  • 当且仅当用户传入的 username 与预设账号完全一致,且 password 与预设密码完全一致时,验证通过并返回 True;
  • 用户名或密码任意一项不匹配(或两者均不匹配)时,返回 False。

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

测试调用 期望返回值 业务说明
login("admin", "python2026") True 用户名与密码均正确,允许登录
login("admin", "wrong_pwd") False 用户名正确但密码错误,拒绝登录
login("guest", "python2026") False 账号不存在,拒绝登录

提示:本练习通过关系运算+逻辑运算返回布尔值。实际工程中登录功能的实现要复杂得多(通常涉及密码哈希单向加密存储、数据库查询校验、防止暴力破解的频次限制以及 Token/会话状态管理等诸多安全机制)。

参考答案

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

python
def calculate_takeout_bill(
    food_total: float, coupon_discount: float, delivery_fee: float
) -> float:
    # 应付总额 = 餐品总额 - 优惠券优惠金额 + 配送费
    total_bill = food_total - coupon_discount + delivery_fee
    return total_bill

# 测试调用
print(calculate_takeout_bill(80, 10, 6))  # 输出: 76
print(calculate_takeout_bill(80, 0, 6))   # 输出: 86
print(calculate_takeout_bill(80, 10, 0))  # 输出: 70

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

python
def calculate_freight(weight: int) -> int:
    # 计费规则:首重 1 公斤收费 12 元;超出 1 公斤的续重部分每公斤加收 5 元
    # 续重公斤数为 weight - 1
    freight = 12 + (weight - 1) * 5
    return freight

# 测试调用
print(calculate_freight(2))  # 输出: 17
print(calculate_freight(4))  # 输出: 27

练习 3:用户登录验证函数

python
def login(username: str, password: str) -> bool:
    # 校验规则:预设账号为 "admin",预设密码为 "python2026"
    # 使用关系运算符 == 比较字符串是否匹配,用逻辑运算符 and 组合两个条件
    # 仅当账号与密码同时正确时,整体表达式计算为 True,否则为 False
    is_authenticated = (username == "admin") and (password == "python2026")
    return is_authenticated

# 测试调用
print(login("admin", "python2026"))  # 输出: True
print(login("admin", "wrong_pwd"))   # 输出: False
print(login("guest", "python2026"))  # 输出: False

第二部分:扩展知识点

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

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

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

调用函数时,除了按照形参的位置顺序逐个传参(位置参数),还可以采用 形参名=数据 的语法显式指定,这种调用方式称为关键字参数(Keyword Arguments)。

使用关键字参数时,实参的传递顺序可以调整,解释器根据参数名称自动对齐:

案例:计算 1 小时 30 分钟对应的总分钟数,对比位置参数调用与关键字参数调用。

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

# 1. 位置参数调用(严格按照 hours 在前、minutes 在后的顺序)
print(to_total_minutes(1, 30))

# 2. 关键字参数调用(显式指定参数名,调换顺序结果相同)
print(to_total_minutes(minutes=30, hours=1))

输出:

text
90
90

当参数较多时,关键字参数指明了每个实参的含义,也能避免因记错位置顺序而传错数据。

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

定义函数时,可以为某些常用参数预设默认值。这种带有预设值的参数称为默认参数(Default Arguments)。

调用该函数时:

  • 若未传入该参数的实参,函数使用预设的默认值;
  • 若传入了新数据,则覆盖默认值。

语法规则:在函数定义中,有默认值的参数必须放在所有无默认值参数之后。若违背该规则(例如写成 def func(a: int = 1, b: int) -> int:),Python 解释器会报出 SyntaxError: non-default argument follows default argument。

设计原因:若允许将默认参数写在前面,当调用者只传入一个实参 func(5) 时,解释器无法判断 5 应当覆盖默认参数 a,还是赋给必填参数 b。为消除匹配歧义,语法规定无默认值的参数必须在前。

案例:计算电商订单实付金额。普通订单默认收取 6.0 元运费;免运费活动订单传入 0.0 覆盖默认运费。

python
def checkout(subtotal: float, shipping_fee: float = 6.0) -> float:
    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: int, discount_rate: float = 1.0
) -> float:
    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.8 一次返回多个结果:多值返回与解包

在 return 语句后用逗号分隔多个表达式,可以一次返回多个结果。

在底层机制上,Python 将这多个值打包为一个**元组(tuple)**对象返回,因此函数本质上仍然只返回一个对象。

返回类型注解 tuple[int, int] 表示返回一个包含两个整数的元组。

接收多值返回结果有两种方式:

  1. 单一变量接收元组:使用一个变量保存完整的打包元组;
  2. 多变量解包(Unpacking):在等号左侧写出对应数量的变量名并用逗号隔开,元组内的元素会按顺序依次赋给各个变量。

案例:将总分钟数换算为“整小时数”与“剩余分钟数”,对比两种接收方式。

python
def split_hour_minute(total_minutes: int) -> tuple[int, int]:
    hours = total_minutes // 60
    minutes = total_minutes % 60
    return hours, minutes

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

# 方式 2:解包到多个变量
h, m = split_hour_minute(95)
print(f"{h} 小时 {m} 分钟")

输出:

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

解包接收时,等号左侧变量数量必须与返回元组中的元素数量严格相等,否则会抛出 ValueError。关于元组的详细操作将在后续章节展开。

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

写在函数体第一行的多行字符串称为文档字符串(docstring),通常使用成对的三引号 """ 书写。

文档字符串与普通注释的区别在于:

  • 普通注释(#)在运行时被解释器忽略;
  • 文档字符串被保留在函数对象的 __doc__ 属性中,可以通过内置函数 help() 查看,在 VS Code 等编辑器中将鼠标悬停在函数名上方也会显示该说明。

案例:为商品小计函数编写文档字符串,并使用 help() 函数查看。

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

    参数:
        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: float, quantity: int) -> float
    根据商品单价与购买数量,计算并返回商品小计金额。
    
    参数:
        unit_price (float): 商品单价(元)
        quantity (int): 购买数量(件)
    
    返回:
        float: 商品小计金额(元)

为核心函数编写文档字符串,有助于后续阅读、维护与代码交接。


扩展知识点练习

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

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

计算规则:

  • 利息计算公式:本金 × 年利率 × 存期年数;
  • 本息合计计算公式:本金 + 利息;
  • 函数同时返回 (利息, 本息合计)。

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

测试调用 期望返回元组 解包打印展示结果
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.9 文档字符串知识。

要求在三引号文档字符串中包含:

  1. 函数功能简述;
  2. 输入参数 weight 的含义、单位及取值约束(weight > 1 的整数);
  3. 返回值的含义与货币单位。

编写完成后,调用 help(calculate_freight) 输出说明文档,确认格式规范、表述清晰。


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