Python 学习笔记(第六期)------函数:定义、参数传递与作用域
更多Python知识:Python基础入门
我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
📖 引言:代码的"积木式"组装
在前五期中,我们掌握了变量、运算符、流程控制和组合数据类型,能够编写出功能完整的程序。但随着程序复杂度提高,一个不可避免的问题出现了------代码重复。
假设你需要在一个程序的不同位置打印三次"欢迎使用本系统"------你会选择把同一段代码复制粘贴三次,还是把这段代码封装起来、需要时直接调用?显然是后者。这不仅减少了代码量,更重要的是------如果将来要修改欢迎语的内容,你只需要改一个地方。
这就是函数的价值。
函数就像一块块"乐高积木",每一块都封装了特定的功能。当你需要完成某个任务时,只需"拼搭"对应的积木即可,无需关心积木内部的构造。本期将系统地学习 Python 中函数的定义与调用、参数传递、返回值、变量作用域,以及递归和匿名函数等高级用法。
--- Compiled and Authored by Whisky --- July 14 th, 2026
📑 本期目录
- 函数概述(为什么需要函数)
- 函数的定义与调用
- 函数参数传递(位置参数、关键字参数、默认参数)
- 参数的打包与解包(
*args与**kwargs) - 混合参数传递
- 函数的返回值
- 变量作用域(局部变量、全局变量、LEGB 原则)
- 特殊形式的函数(递归函数、匿名函数 lambda)
- 综合实战演练(三个完整案例)
- 总结与知识点一览表
正文
一、函数概述
1.1 为什么需要函数?
在实际开发中,如果若干段代码的执行逻辑完全相同,将这些代码抽取成函数可以带来多重好处:
| 好处 | 说明 |
|---|---|
| 减少冗余 | 相同的逻辑只需编写一次,多处调用 |
| 提高可维护性 | 修改功能时只需改函数内部,调用处无需改动 |
| 模块化 | 将复杂程序拆解为多个小功能,每个函数专注一件事 |
| 代码复用 | 写好的函数可以在不同项目中重复使用 |
| 便于测试 | 每个函数可以独立测试,定位 bug 更容易 |
1.2 使用函数前 vs 使用函数后
未使用函数(重复代码):
python
# 打印边长为2的正方形
for i in range(2):
for j in range(2):
print("*", end="")
print()
# 打印边长为3的正方形
for i in range(3):
for j in range(3):
print("*", end="")
print()
# 打印边长为4的正方形
for i in range(4):
for j in range(4):
print("*", end="")
print()
使用函数后(封装复用):
python
def print_square(length):
"""打印指定边长的正方形"""
for i in range(length):
for j in range(length):
print("*", end="")
print()
# 调用函数,一行搞定
print_square(2)
print_square(3)
print_square(4)
💡 白话理解:函数就像一个"自动化工具"。你只需要告诉它"做什么"(输入参数),它就会帮你完成"怎么做"的具体细节,最后把结果给你。你不必关心工具内部如何运转,只需要知道如何使用它。
二、函数的定义与调用
2.1 定义函数
Python 使用 def 关键字定义函数,语法格式如下:
python
def 函数名([参数列表]):
"""文档字符串(可选)"""
函数体
[return 返回值]
组成部分说明:
| 组成部分 | 说明 | 是否必须 |
|---|---|---|
def |
定义函数的关键字 | ✅ 必须 |
| 函数名 | 函数的唯一标识,遵循标识符命名规则 | ✅ 必须 |
() |
包裹参数列表 | ✅ 必须 |
| 参数列表 | 接收外部传入的数据 | ❌ 可选 |
: |
标记函数体开始 | ✅ 必须 |
| 文档字符串 | 说明函数的功能,用三引号包裹 | ❌ 建议 |
| 函数体 | 实现具体功能的代码 | ✅ 必须 |
return |
返回结果给调用方 | ❌ 可选 |
python
def add():
"""无参数函数:计算 11 + 22"""
result = 11 + 22
print(result)
def add_modify(a, b):
"""有参数函数:计算两个数的和"""
result = a + b
print(result)
2.2 调用函数
函数定义后不会自动执行,只有在被调用时才会执行。
python
# 调用无参函数
add() # 输出:33
# 调用有参函数
add_modify(10, 20) # 输出:30
函数调用执行流程:
text
程序执行到函数调用处
↓
暂停当前代码执行
↓
将实参传递给形参
↓
进入函数体执行
↓
执行完毕(或遇到 return)
↓
回到暂停处继续执行
2.3 嵌套调用与嵌套定义
嵌套调用:函数内部调用另一个函数。
python
def add():
result = 11 + 22
print(result)
def add_modify(a, b):
result = a + b
add() # 嵌套调用 add()
print(result)
add_modify(10, 20)
# 输出:
# 33(来自 add())
# 30(来自 add_modify())
嵌套定义:在函数内部定义另一个函数。内层函数只能在外层函数内部被调用。
python
def outer(a, b):
result = a + b
def inner(): # 嵌套定义
print("这是内层函数")
inner() # 外层函数内调用内层函数
print(result)
outer(10, 20)
# 输出:
# 这是内层函数
# 30
# inner() # ❌ 外部无法直接调用内层函数
⚠️ 注意:内层函数的作用域仅限于外层函数内部,外部代码无法直接调用。
三、函数参数传递
我们将定义函数时设置的参数称为形式参数(形参) ,将调用函数时传入的参数称为实际参数(实参)。参数传递就是将实参传递给形参的过程。
3.1 位置参数传递
实参按照位置顺序依次传递给形参------第一个实参传给第一个形参,第二个传给第二个,以此类推。
python
def get_max(a, b):
if a > b:
print(f"最大值:{a}")
else:
print(f"最大值:{b}")
get_max(5, 8) # 5 传给 a,8 传给 b → 最大值:8
get_max(8, 5) # 8 传给 a,5 传给 b → 最大值:8
⚠️ 注意:位置参数的顺序非常重要。调换顺序可能导致逻辑错误。
3.2 关键字参数传递
通过 形参名=实参值 的格式显式指定实参与形参的对应关系,顺序无关紧要。
python
def connect(ip, port):
print(f"连接设备 {ip}:{port}")
# 以下三种调用方式等价
connect("127.0.0.1", 8080) # 位置参数
connect(ip="127.0.0.1", port=8080) # 关键字参数
connect(port=8080, ip="127.0.0.1") # 关键字参数(顺序可调换)
3.3 位置参数与关键字参数的混合
Python 3.8+ 引入了仅限位置参数 (/)和仅限关键字参数 (*)的语法:
python
def demo(a, b, /, c, *, d, e):
print(a, b, c, d, e)
# a 和 b 只能按位置传递
# c 可以按位置或关键字传递
# d 和 e 只能按关键字传递
demo(1, 2, 3, d=4, e=5) # ✅ 正确
demo(1, 2, c=3, d=4, e=5) # ✅ 正确
# demo(1, 2, 3, 4, 5) # ❌ d 和 e 必须用关键字
3.4 默认参数
函数定义时可以为形参指定默认值。调用时若未传入对应实参,则使用默认值。
python
def connect(ip, port=8080):
print(f"连接设备 {ip}:{port}")
connect("127.0.0.1") # 使用默认 port=8080 → 连接设备 127.0.0.1:8080
connect("127.0.0.1", 3306) # 覆盖默认值 → 连接设备 127.0.0.1:3306
connect(ip="127.0.0.1", port=3306)
⚠️ 注意 :带有默认值的参数必须位于普通参数之后。
python
# ❌ 错误:默认参数不能在普通参数前面
def wrong(a=1, b): # SyntaxError
pass
# ✅ 正确
def correct(b, a=1):
pass
四、参数的打包与解包
4.1 打包------接收不定数量的参数
当函数无法预知会传入多少个参数时,可以使用 *args 和 **kwargs 进行"打包"。
| 符号 | 用途 | 打包成的类型 | 命名建议 |
|---|---|---|---|
* |
接收多个位置参数 | 元组(tuple) | *args |
** |
接收多个关键字参数 | 字典(dict) | **kwargs |
python
# *args:打包位置参数为元组
def test_args(*args):
print(args)
test_args(11, 22, 33, 44, 55) # (11, 22, 33, 44, 55)
# **kwargs:打包关键字参数为字典
def test_kwargs(**kwargs):
print(kwargs)
test_kwargs(a=1, b=2, c=3, d=4) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
💡 命名约定 :虽然
*和**后面的名称可以任意,但社区约定使用*args和**kwargs,这是 Python 程序员普遍识别的写法。如果没有接收到任何参数,args为空元组(),kwargs为空字典{}。
4.2 解包------将序列/字典拆分为实参
打包的反向操作------将序列或字典"拆开",作为实参传递给函数。
python
def test(A, B, C, D):
print(A, B, C, D)
# 元组解包:每个元素对应一个位置参数
numbers = (10, 20, 30, 40)
test(*numbers) # 10 20 30 40
# 字典解包:键名必须与形参名一致
dic = {'A': 11, 'C': 22, 'B': 33, 'D': 44}
test(**dic) # 11 33 22 44
⚠️ 注意 :字典解包时,字典的键名必须与函数的形参名完全一致,否则会报错。
五、混合参数传递
在实际开发中,多种参数传递方式可以混合使用,但必须遵循以下顺序规则。
定义函数时的顺序:
text
普通参数 → 默认参数 → *args → **kwargs
python
def mixed(a, b, c=33, *args, **kwargs):
print(f"a={a}, b={b}, c={c}, args={args}, kwargs={kwargs}")
mixed(1, 2) # a=1, b=2, c=33, args=(), kwargs={}
mixed(1, 2, 3) # a=1, b=2, c=3, args=(), kwargs={}
mixed(1, 2, 3, 4) # a=1, b=2, c=3, args=(4,), kwargs={}
mixed(1, 2, 3, 4, e=5) # a=1, b=2, c=3, args=(4,), kwargs={'e': 5}
mixed(1, 2, e=5) # a=1, b=2, c=33, args=(), kwargs={'e': 5}
调用函数时的顺序:
text
位置参数 → 关键字参数 → *args 解包 → **kwargs 解包
💡 记忆口诀:定义时,普通在前、默认中间、打包在最后;调用时,位置在前、关键字在后。
六、函数的返回值
函数使用 return 语句将处理结果返回给调用方,同时函数执行结束。
6.1 返回单个值
python
def add(a, b):
return a + b
result = add(10, 20)
print(result) # 30
6.2 返回多个值(封装为元组)
python
def move(x, y, step):
next_x = x + step
next_y = y - step
return next_x, next_y # 返回元组 (next_x, next_y)
result = move(10, 20, 5)
print(result) # (15, 15)
print(type(result)) # <class 'tuple'>
# 元组解包接收
x, y = move(10, 20, 5)
print(x, y) # 15 15
6.3 没有 return 时返回 None
python
def no_return():
print("没有 return 语句")
result = no_return()
print(result) # None
七、变量作用域
变量并非在程序的任意位置都可以被访问,其可访问的范围称为作用域。
7.1 局部变量 vs 全局变量
| 类型 | 定义位置 | 作用范围 | 生命周期 |
|---|---|---|---|
| 局部变量 | 函数内部 | 仅限函数内部 | 函数执行结束即释放 |
| 全局变量 | 函数外部 | 整个程序 | 程序运行期间一直存在 |
python
# 全局变量
global_var = 100
def test():
# 局部变量
local_var = 10
print(f"函数内访问全局变量:{global_var}") # ✅ 可以读取
print(f"局部变量:{local_var}") # ✅ 可以访问
test()
print(global_var) # ✅ 可以访问
# print(local_var) # ❌ NameError:函数外部无法访问局部变量
7.2 全局变量的修改------global 关键字
在函数内部读取 全局变量是允许的,但直接修改会报错:
python
num = 10
def test():
num += 1 # ❌ UnboundLocalError
print(num)
test()
报错原因 :函数内部的 num += 1 被 Python 解释器视为声明了一个新的局部变量 num,但在赋值之前就被引用了,所以报错。
解决方案 :使用 global 关键字声明。
python
num = 10
def test():
global num # 声明 num 为全局变量
num += 1
print(num)
test() # 11
print(num) # 11(全局变量被修改了)
7.3 嵌套作用域的修改------nonlocal 关键字
在嵌套函数中,使用 nonlocal 可以修改外层函数的局部变量。
python
def outer():
num = 10
def inner():
nonlocal num # 声明 num 来自外层函数
num += 1
print(f"内层:{num}")
inner() # 内层:11
print(f"外层:{num}") # 外层:11
outer()
7.4 LEGB 原则
Python 在查找变量时遵循 LEGB 顺序:
| 缩写 | 全称 | 说明 |
|---|---|---|
| L | Local | 局部作用域(函数内部) |
| E | Enclosing | 嵌套作用域(外层函数的局部作用域) |
| G | Global | 全局作用域(模块级别) |
| B | Built-in | 内置作用域(Python 内置的标识符) |
python
import math # Built-in
g_num = 10 # Global
def outer(): # Enclosing
e_num = 20
def inner(): # Local
l_num = 30
print(l_num) # Local
print(e_num) # Enclosing
print(g_num) # Global
print(math.pi) # Built-in
inner()
outer()
💡 白话理解:LEGB 可以理解为"就近原则"------Python 从最近的区域开始找变量,找不到就往外层找,直到内置作用域。就像你在家里找钥匙------先看口袋(Local),再看客厅(Enclosing),然后看整个屋子(Global),最后才去物业(Built-in)问。
八、特殊形式的函数
8.1 递归函数
递归函数是指在函数内部调用自身的函数。递归需要满足两个条件:
| 条件 | 说明 | 示例(阶乘) |
|---|---|---|
| 边界条件 | 递归终止的条件,不能再继续分解 | n == 1 时返回 1 |
| 递归公式 | 将原问题分解为更小的子问题 | n! = n * (n-1)! |
递归执行的两个阶段:
text
递推:将问题不断分解为更小的子问题,直到达到边界条件
回溯:从边界条件开始,逐层返回计算结果
案例:阶乘 n!
python
def factorial(n):
if n == 1: # 边界条件
return 1
else: # 递归公式
return n * factorial(n - 1)
result = factorial(5)
print(f"5! = {result}") # 120
执行过程追踪:
text
factorial(5)
return 5 * factorial(4)
return 4 * factorial(3)
return 3 * factorial(2)
return 2 * factorial(1)
return 1
return 2 * 1 = 2
return 3 * 2 = 6
return 4 * 6 = 24
return 5 * 24 = 120
案例:斐波那契数列(兔子数列)
斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, ...,从第3项开始,每一项等于前两项之和。
python
def fibonacci(n):
if n == 1 or n == 2: # 边界条件
return 1
else: # 递归公式
return fibonacci(n - 1) + fibonacci(n - 2)
# 输出前10项
for i in range(1, 11):
print(fibonacci(i), end=" ") # 1 1 2 3 5 8 13 21 34 55
⚠️ 性能提示:递归虽然代码简洁,但存在效率问题(如斐波那契数列有大量重复计算)。对于大规模计算,迭代(循环)通常更高效。
8.2 匿名函数(lambda)
lambda 函数是一种没有名称的函数,用于定义简单的、单行表达式的功能。
python
lambda 参数列表: 表达式
与普通函数的对比:
| 对比维度 | 普通函数(def) | 匿名函数(lambda) |
|---|---|---|
| 名称 | 有名称 | 无名称 |
| 函数体 | 多条语句 | 只能有一个表达式 |
| 返回值 | 可用 return 显式返回 |
表达式的结果自动作为返回值 |
| 复杂度 | 可实现复杂功能 | 仅限简单功能 |
python
# 定义匿名函数并赋值给变量
square = lambda x: x ** 2
print(square(10)) # 100
# 多个参数
add = lambda x, y: x + y
print(add(10, 20)) # 30
# 直接在调用处使用
result = (lambda x, y: x * y)(5, 6)
print(result) # 30
lambda 的典型应用场景 :作为 sorted()、map()、filter() 等函数的参数。
python
# 按字典的某个字段排序
students = [
{'name': '张三', 'score': 85},
{'name': '李四', 'score': 92},
{'name': '王五', 'score': 78}
]
# 按成绩升序排序
sorted_students = sorted(students, key=lambda x: x['score'])
print(sorted_students)
# [{'name': '王五', 'score': 78}, {'name': '张三', 'score': 85}, {'name': '李四', 'score': 92}]
九、综合实战演练
实战一:角谷猜想(冰雹猜想)
需求描述:角谷猜想是指:对于任意一个正整数 n,如果 n 是偶数,则变为 n/2;如果 n 是奇数,则变为 3n+1。重复此过程,最终会得到 1。本程序计算经过多少步可变为 1。
涉及知识点:函数定义、递归函数、条件判断
python
def collatz_steps(n, steps=0):
"""
计算角谷猜想的步数
参数:
n: 当前数字
steps: 已走过的步数
返回:
最终步数
"""
if n == 1:
return steps
elif n % 2 == 0:
return collatz_steps(n // 2, steps + 1)
else:
return collatz_steps(3 * n + 1, steps + 1)
# 测试
num = int(input("请输入一个正整数:"))
step_count = collatz_steps(num)
print(f"{num} 经过 {step_count} 步变为 1")
运行示例:
text
请输入一个正整数:12
12 经过 9 步变为 1
执行过程:12 → 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1
实战二:饮料自动售货机
需求描述:实现一个饮料自动售货机程序,具备以下功能:
- 显示所有饮品的名称、价格和库存
- 用户选择饮品和购买数量
- 计算总价并完成购买(更新库存)
涉及知识点:函数定义、字典操作、参数传递、返回值
python
# 饮料自动售货机
# 初始化库存(字典)
inventory = {
"可乐": {"price": 3.5, "stock": 10},
"雪碧": {"price": 3.0, "stock": 8},
"矿泉水": {"price": 2.0, "stock": 15},
"咖啡": {"price": 5.5, "stock": 5}
}
def show_products():
"""显示所有饮品信息"""
print("\n" + "=" * 40)
print(" 🥤 饮品列表")
print("=" * 40)
print("序号\t名称\t\t价格\t库存")
print("-" * 40)
idx = 1
for name, info in inventory.items():
print(f"{idx}\t{name}\t\t¥{info['price']:.1f}\t{info['stock']} 瓶")
idx += 1
print("=" * 40)
def buy_product(name, quantity):
"""
购买饮品
参数:
name: 饮品名称
quantity: 购买数量
返回:
(success, message, total_price)
"""
if name not in inventory:
return False, "该饮品不存在!", 0
info = inventory[name]
if info["stock"] < quantity:
return False, f"库存不足!当前库存:{info['stock']} 瓶", 0
total = info["price"] * quantity
info["stock"] -= quantity
return True, f"购买成功!共需支付 ¥{total:.2f}", total
def main():
"""主程序"""
print("=" * 40)
print(" 欢迎使用自动售货机")
print("=" * 40)
while True:
show_products()
choice = input("请输入序号选择饮品(输入 0 退出):")
if choice == "0":
print("感谢使用,再见!")
break
# 根据序号获取饮品名称
names = list(inventory.keys())
try:
idx = int(choice) - 1
if idx < 0 or idx >= len(names):
print("❌ 无效序号!")
continue
name = names[idx]
except ValueError:
print("❌ 请输入有效数字!")
continue
try:
quantity = int(input(f"请输入购买数量({name}):"))
if quantity <= 0:
print("❌ 数量必须大于 0!")
continue
except ValueError:
print("❌ 请输入有效数字!")
continue
success, message, total = buy_product(name, quantity)
print(f"✅ {message}")
main()
运行示例:
text
========================================
欢迎使用自动售货机
========================================
========================================
🥤 饮品列表
========================================
序号 名称 价格 库存
----------------------------------------
1 可乐 ¥3.5 10 瓶
2 雪碧 ¥3.0 8 瓶
3 矿泉水 ¥2.0 15 瓶
4 咖啡 ¥5.5 5 瓶
========================================
请输入序号选择饮品(输入 0 退出):1
请输入购买数量(可乐):3
✅ 购买成功!共需支付 ¥10.50
实战三:归并排序(递归经典应用)
需求描述:归并排序是一种基于"分治策略"的排序算法------将序列不断二分,直到每个子序列长度为 1,然后两两合并成有序序列。
涉及知识点:递归函数、列表操作、函数嵌套
python
# 归并排序
def merge_sort(arr):
"""归并排序主函数"""
if len(arr) <= 1:
return arr
# 1. 分解:将数组分为两半
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
# 2. 合并:将两个有序数组合并为一个
return merge(left, right)
def merge(left, right):
"""合并两个有序数组"""
result = []
i = j = 0
# 比较两个数组的元素,依次放入结果
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# 剩余元素直接追加
result.extend(left[i:])
result.extend(right[j:])
return result
# 测试
data = [8, 4, 5, 7, 1, 3, 6, 2]
print(f"原始数据:{data}")
sorted_data = merge_sort(data)
print(f"排序结果:{sorted_data}")
运行结果:
text
原始数据:[8, 4, 5, 7, 1, 3, 6, 2]
排序结果:[1, 2, 3, 4, 5, 6, 7, 8]
执行流程:
text
[8, 4, 5, 7, 1, 3, 6, 2]
/ \
[8, 4, 5, 7] [1, 3, 6, 2]
/ \ / \
[8, 4] [5, 7] [1, 3] [6, 2]
/ \ / \ / \ / \
[8] [4] [5] [7] [1] [3] [6] [2]
\ / \ / \ / \ /
[4, 8] [5, 7] [1, 3] [2, 6]
\ / \ /
[4, 5, 7, 8] [1, 2, 3, 6]
\ /
[1, 2, 3, 4, 5, 6, 7, 8]
十、补充知识:函数式编程思想
函数式编程是一种编程范式,强调使用纯函数 (无副作用、相同输入永远得到相同输出)和不可变数据。虽然 Python 不是纯函数式语言,但吸收了很多函数式编程的特性:
| 特性 | Python 支持 | 说明 |
|---|---|---|
| 高阶函数 | ✅ map()、filter()、reduce() |
函数可以作为参数传递或返回值 |
| 匿名函数 | ✅ lambda |
快速定义简单的单行函数 |
| 列表推导式 | ✅ [x for x in ...] |
替代 map() 和 filter() 的常见用法 |
| 不可变数据 | ✅ 元组、字符串、数字 | 虽然 Python 有可变类型,但也支持不可变类型 |
python
# 使用 map + lambda 对列表每个元素平方
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# 使用 filter + lambda 过滤偶数
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]
# 使用列表推导式(更 Pythonic)
squared = [x ** 2 for x in numbers]
print(squared) # [1, 4, 9, 16, 25]
📝 总结与知识点一览表
核心知识点汇总
| 类别 | 知识点 | 关键语法 |
|---|---|---|
| 函数定义 | def + 函数名 + 参数 + 函数体 |
def func(): |
| 参数传递 | 位置参数、关键字参数、默认参数 | def func(a, b=1): |
| 参数打包 | 接收不定数量参数 | *args、**kwargs |
| 参数解包 | 拆分序列/字典作为实参 | *list、**dict |
| 返回值 | 返回结果并结束函数 | return value |
| 局部变量 | 函数内部定义 | x = 10(函数内) |
| 全局变量 | 函数外部定义 | x = 10(函数外) |
| 全局修改 | 函数内修改全局变量 | global x |
| 嵌套修改 | 内层函数修改外层变量 | nonlocal x |
| 递归函数 | 函数调用自身 | 边界条件 + 递归公式 |
| 匿名函数 | 单行表达式函数 | lambda x: x*2 |
| 高阶函数 | 函数作为参数/返回值 | map()、filter() |
常用语法速查
| 需求 | 代码示例 |
|---|---|
| 定义无参函数 | def hello(): print("Hello") |
| 定义有参函数 | def add(a, b): return a + b |
| 默认参数 | def connect(ip, port=8080): |
| 不定长参数 | def sum_all(*args): return sum(args) |
| 关键字参数打包 | def show(**kwargs): print(kwargs) |
| 返回多个值 | return a, b, c(自动打包为元组) |
| 全局变量修改 | global x; x += 1 |
| 匿名函数 | lambda x, y: x + y |
| 递归函数 | def fact(n): return 1 if n==1 else n*fact(n-1) |
常见错误排查指南
| 错误现象 | 大概率原因 | 解决方案 |
|---|---|---|
NameError: name 'x' is not defined |
访问了未定义的变量 | 检查变量是否已赋值或作用域是否正确 |
UnboundLocalError: local variable 'x' referenced before assignment |
函数内试图修改全局变量,但未用 global 声明 |
在修改前添加 global x |
TypeError: func() missing 1 required positional argument |
调用函数时参数数量不足 | 检查函数定义,传入所有必需的参数 |
TypeError: func() got an unexpected keyword argument 'x' |
传入了函数不认识的形参名 | 检查形参名是否拼写正确 |
RecursionError: maximum recursion depth exceeded |
递归函数没有正确的边界条件 | 检查边界条件是否可达到,或递归层数过大 |
返回值为 None 但期望有值 |
函数中没有 return 或 return 后没有值 |
添加正确的 return 语句 |
学习/生产最佳实践
- 单一职责原则:每个函数只做一件事,保持函数短小精悍(建议不超过 30~50 行)。如果一个函数做太多事,拆分成多个小函数。
- 文档字符串:为每个函数添加文档字符串,说明功能、参数和返回值。这在团队协作和后期维护中非常重要。
python
def calculate_bmi(weight, height):
"""
计算身体质量指数(BMI)
参数:
weight: 体重(公斤)
height: 身高(米)
返回:
float: BMI 值
"""
return weight / (height ** 2)
- 善用默认参数:为常用参数设置合理的默认值,提升函数的易用性。
- 避免修改全局变量:尽量通过参数传递和返回值与函数交互,而不是直接操作全局变量。这会使代码更可预测、更易测试。
- 递归 vs 迭代:递归代码更简洁,但存在性能开销和栈溢出风险。对于大规模数据,优先考虑迭代实现。
- 函数命名规范 :函数名应使用小写字母和下划线(如
get_user_info),动词开头,体现函数的行为。
🔜 下期预告
本期我们系统学习了函数的定义与调用、参数传递(位置/关键字/默认/打包/解包)、返回值、变量作用域(LEGB + global/nonlocal),以及递归函数和匿名函数(lambda)等高级用法,并通过角谷猜想、饮料自动售货机和归并排序三个实战项目,将函数式编程思想应用到实际场景中。
下一期 ,我们将进入 文件与数据格式化 的学习------掌握如何将程序中的数据持久化存储到文件中,学习文件的打开、读写、关闭操作,了解文本文件与二进制文件的区别,以及 CSV、JSON 等常见数据格式的读写方法。通过"文件备份工具"和"用户账户管理系统"等实战项目,让程序的数据真正"留得住"。敬请期待!
--- Compiled and Authored by Whisky --- July 14 th, 2026