系列 · Python 零基础到简单脚本 第 6 篇 / 共 11 篇 教程

Python 函数图解:参数、局部变量与 return 怎样流动

沿一条订单金额调用轨迹,看见实参怎样进入参数名、局部变量何时存在、return 怎样回到调用位置,以及 print 和可变默认参数的真实去向。

作者:黄撑 更新于 2026-08-27
本文目录 7 节 · 点击展开

循环让同一段代码依次处理多条订单。函数则给一段处理逻辑起名字,并为它划出清楚的入口、内部空间和出口。

真正调用函数时,数据并不是“突然出现在函数里”。实参会先进入参数名,函数内部创建局部变量,return 再把结果送回原来的调用位置。这一篇就沿着一笔订单金额看完整条路径。

定义函数,不等于已经执行函数体

先定义一个订单金额函数:

def calculate_order_amount(price, quantity, discount_rate=0.0):
    subtotal = price * quantity
    discount = subtotal * discount_rate
    final_amount = subtotal - discount
    return final_amount

执行这段 def 时,Python 只是记住:以后可以用 calculate_order_amount 这个名字调用这段逻辑。函数体里的乘法、减法和 return 还没有运行。

真正的处理从调用开始:

def calculate_order_amount(price, quantity, discount_rate=0.0):
    subtotal = price * quantity
    discount = subtotal * discount_rate
    final_amount = subtotal - discount
    return final_amount


unit_price = 129.0
order_quantity = 2

amount = calculate_order_amount(
    unit_price,
    order_quantity,
    discount_rate=0.1,
)

print(amount)

输出:

232.2

一次调用怎样进去,再带着结果出来

下面的调用轨迹分成两个空间:左右两端是函数外的调用方,中间带边框的区域是本次调用临时打开的函数空间。

调用位置 → 参数 → 局部变量 → return → 原调用位置折后金额 232.2 的完整来路
01 · 调用位置calculate_order_amount(129.0, 2, discount_rate=0.1)
实参
129.0
实参
2
实参
0.1

先求出三个实参的值,再打开一次函数调用。

函数内部 · 本次调用的局部空间
02 · 参数绑定price → 129.0quantity → 2discount_rate → 0.1

外部实参进入函数定义中的参数名。

03 · 局部计算subtotal → 258.0discount → 25.8final_amount → 232.2

这些名字只服务于本次调用。

04 · returnreturn final_amount送出 232.2

执行到 return,函数体立即结束。

05 · 回到原调用位置amount = 232.2
保留
unit_price · order_quantity
新增
amount

返回值替换原来的调用表达式,再绑定给 amount。

调用结束后仍存在unit_price · order_quantity · amount
函数空间关闭后不可从外部访问price · quantity · subtotal · discount · final_amount

这条轨迹同时解释了三个常见词:

名称在本例中是什么什么时候存在
实参unit_priceorder_quantity0.1 的实际值调用位置先求值
参数pricequantitydiscount_rate函数调用期间
局部变量subtotaldiscountfinal_amount执行到对应赋值行后,直到本次调用结束

前两个实参按位置进入 pricequantitydiscount_rate=0.1 是关键字参数,它明确指定进入哪个参数。若调用时不写折扣率,就使用定义中的默认值 0.0

full_amount = calculate_order_amount(129.0, 2)
print(full_amount)  # 258.0

局部变量不会自动跑到函数外面

subtotal 在函数内部确实得到过 258.0,但调用结束后,外部代码不能直接用这个局部名字:

amount = calculate_order_amount(129.0, 2)

print(amount)    # 258.0
print(subtotal)  # NameError: name 'subtotal' is not defined

需要带到外面的结果,应通过 return 送出,再由调用方使用自己的变量名接住。这个边界能防止函数内部的临时名字散落到整份脚本里。

下面两个函数都算出 258.0,但结果的去向不同:

def print_amount(price, quantity):
    subtotal = price * quantity
    print(subtotal)


def return_amount(price, quantity):
    subtotal = price * quantity
    return subtotal


printed_result = print_amount(129.0, 2)
returned_result = return_amount(129.0, 2)

print(printed_result)          # None
print(returned_result * 1.1)  # 283.8
结果去向对照屏幕上看见结果,不等于调用方拿到了结果
print(subtotal)显示路径
  1. 01 · 函数内部subtotal → 258.0
  2. 02 · 终端输出屏幕显示 258.0
  3. 03 · 调用表达式的值None函数没有 return
  4. 04 · 外部状态printed_result → None不能拿来继续算金额
return subtotal返回路径
  1. 01 · 函数内部subtotal → 258.0
  2. 02 · return258.0 送回调用位置
  3. 03 · 外部状态returned_result → 258.0
  4. 04 · 后续计算258.0 * 1.1 → 283.8返回值可以继续流动

print() 适合调试、提示和最终展示;return 适合把计算结果交给后面的代码。数据处理函数通常应该返回结果,由最外层代码决定何时显示。

可变默认参数为什么记住了上一次调用

数字和文字用作默认参数通常很直观,列表则需要格外小心。下面的写法只在执行 def 时创建一次默认列表:

def remember_order(order_no, history=[]):
    history.append(order_no)
    return history


print(remember_order("A001"))
print(remember_order("A002"))

很多人会以为两次调用各有一张空列表,实际输出却是:

['A001']
['A001', 'A002']
定义时状态 → 第 1 次调用 → 第 2 次调用默认列表是否被两次调用共享
有风险的写法history=[]
  1. 01 · 定义函数创建默认列表 L1L1 → []
  2. 02 · 调用 A001history 继续指向 L1L1 → [“A001”]
  3. 03 · 调用 A002再次拿到同一个 L1L1 → [“A001”, “A002”]

函数调用结束了,但函数定义仍保留着对默认列表 L1 的引用。

安全的写法history=None
  1. 01 · 定义函数默认值只是 None不提前创建列表
  2. 02 · 调用 A001本次创建列表 L1L1 → [“A001”]
  3. 03 · 调用 A002下次创建新列表 L2L2 → [“A002”]

每次没有传 history 时,都在函数内部创建一张新列表。

安全写法是把 None 当作“调用方没有传列表”的信号:

def remember_order(order_no, history=None):
    if history is None:
        history = []

    history.append(order_no)
    return history


print(remember_order("A001"))
print(remember_order("A002"))

输出:

['A001']
['A002']

如果调用方明确传入一张列表,函数仍会使用那张列表。这种写法修复的是“省略参数时意外共享默认对象”,不是禁止函数接收列表。

可选补充参数数量真的不固定时,再使用 *args
def total_amount(*amounts):
    total = 0.0

    for amount in amounts:
        total += amount

    return total


print(total_amount(129.0, 88.0, 199.0))  # 416.0

函数内部的 amounts 是由所有位置实参组成的元组。订单金额函数的参数数量固定时,直接写 pricequantity 会比 *args 更清楚。

什么时候值得写成函数

函数不是为了把代码切得越碎越好。出现这些信号时,再考虑提取:

  • 同一段逻辑会被重复调用。
  • 一段处理能用清楚的动作命名。
  • 输入和返回结果可以明确说清。
  • 提取后,主流程更容易顺着读下去。

例如 calculate_order_amount 清楚表达了输入是价格、数量和折扣率,输出是最终金额。若某一行只出现一次,而且内联更直观,就不必为了形式增加函数。

动手练习:先画调用轨迹,再运行

复制本章的 calculate_order_amount,完成下面四步:

  1. 调用 calculate_order_amount(88.0, 3),写出参数绑定、三个局部变量和返回值。
  2. 改用 discount_rate=0.2,先预测 subtotaldiscountfinal_amount,再运行确认。
  3. return final_amount 暂时改成 print(final_amount),解释调用方变量为什么会得到 None
  4. 分别运行有风险和安全的 remember_order 两次,指出第二次调用开始前默认列表是什么状态。