Python: 参数进阶与作用域
函数基础学会了,这一课来点进阶技巧。参数有多少种写法?"变量作用域"是什么意思?递归又是什么?这些概念掌握后,你对函数的理解会上一个台阶。
1. 默认参数
给参数指定默认值,调用时可以不传:
▶ 示例:默认参数基本用法
def greet(name, greeting="你好"):
print(f"{greeting},{name}!")
greet("Alice") # 你好,Alice!
greet("Bob", "Hello") # Hello,Bob!
greet("Charlie", "嗨") # 嗨,Charlie!
输出:
# 函数定义成功
(1) ⚠️ 默认参数的陷阱
默认参数的值只在函数定义时计算一次,而不是每次调用时。如果默认值是可变对象(如列表、字典),会有意想不到的行为:
▶ 示例:可变默认参数的陷阱
def add_item(item, items=[]): # ❌ 危险!
items.append(item)
return items
print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['苹果', '香蕉'] —— 不是 ['香蕉']!
print(add_item("橘子")) # ['苹果', '香蕉', '橘子']
输出:
# 函数定义成功
原因: items=[] 这个空列表在函数定义时创建了一次,之后的每次调用都在同一个列表上追加。
正确做法: 用 None 作为默认值,在函数内部创建新列表:
▶ 示例:安全的默认参数写法
def add_item(item, items=None): # ✅ 安全
if items is None:
items = []
items.append(item)
return items
print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['香蕉']
print(add_item("橘子")) # ['橘子']
输出:
# 函数定义成功
None、数字、字符串、元组)。不要用列表、字典等可变对象做默认值。
2. 关键字参数
调用函数时,可以通过参数名来指定值,这样可以不按顺序传参:
▶ 示例:关键字参数调用
def create_user(name, age, city):
print(f"用户:{name},{age}岁,来自{city}")
# 位置参数——必须按顺序
create_user("Alice", 25, "北京")
# 关键字参数——指定参数名,顺序无关
create_user(age=30, city="上海", name="Bob")
create_user(name="Charlie", city="广州", age=22)
# 混合使用——位置参数在前,关键字参数在后
create_user("赵六", city="深圳", age=28)
# create_user(name="孙七", 35, "成都") ❌ 关键字参数不能在位置参数前面
输出:
# 函数定义成功
3. *args:可变位置参数
如果你不确定函数会接收多少个参数,用 *args:
▶ 示例:*args 实现求和函数
def sum_all(*numbers):
"""接收任意多个数字,返回它们的和"""
total = 0
for n in numbers:
total += n
return total
print(sum_all(1, 2)) # 3
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0——没有参数也可以
输出:
# 函数定义成功
*args 把所有传入的位置参数打包成一个元组:
▶ 示例:查看 args 中的参数
def show_args(*args):
print(f"接收到 {len(args)} 个参数:{args}")
for i, arg in enumerate(args, 1):
print(f" 第{i}个:{arg}")
show_args("苹果", "香蕉", "橘子")
输出:
# 函数定义成功
4. **kwargs:可变关键字参数
**kwargs 接收任意多个关键字参数,打包成一个字典:
▶ 示例:**kwargs 构建用户资料
def create_profile(**info):
"""创建用户资料,接受任意多的信息"""
print("用户资料:")
for key, value in info.items():
print(f" {key}:{value}")
create_profile(name="Alice", age=25, city="北京", job="工程师")
运行结果:
用户资料:
name:Alice
age:25
city:北京
job:工程师
▶ 示例:综合使用 *args 和 **kwargs
def advanced_function(a, b, *args, **kwargs):
print(f"位置参数:a={a}, b={b}")
print(f"额外位置参数:{args}")
print(f"关键字参数:{kwargs}")
advanced_function(1, 2, 3, 4, 5, name="Alice", age=25)
# 位置参数:a=1, b=2
# 额外位置参数:(3, 4, 5)
# 关键字参数:{'name': 'Alice', 'age': 25}
输出:
# 函数定义成功
普通参数 → *args → 默认参数 → **kwargs
5. 作用域:LEGB 规则
变量在函数的什么位置能访问?这由作用域决定。Python 有四个层级的作用域:
▶ 示例:LEGB 作用域嵌套
# 全局作用域
x = 10 # 全局变量
def outer():
# 外层函数作用域
x = 20 # 这是 outer 的局部变量,和全局的 x 不是同一个
def inner():
# 局部作用域
x = 30 # 这是 inner 的局部变量
print(f"inner: {x}") # 30
inner()
print(f"outer: {x}") # 20
outer()
print(f"global: {x}") # 10
输出:
# 函数定义成功
LEGB 代表:
- Local(局部)—— 当前函数内部
- Enclosing(外层)—— 外层函数的局部
- Global(全局)—— 模块级别
- Built-in(内置)—— Python 内置函数(如
print、len)
Python 按 L → E → G → B 的顺序查找变量。
(1) global 和 nonlocal
如果你想在函数内部修改全局变量,用 global:
▶ 示例:使用 global 修改全局变量
count = 0
def increment():
global count # 声明要操作全局变量
count += 1
increment()
increment()
print(count) # 2
输出:
# 函数定义成功
global。 全局变量让代码变得难以理解和调试。函数应该通过参数接收输入,通过 return 返回输出,而不是偷偷修改全局变量。
6. 递归入门
递归就是函数调用自己。递归的核心是:把一个大问题拆成更小的同类问题。
▶ 示例:递归计算阶乘
def factorial(n):
"""计算 n 的阶乘(n!)"""
if n <= 1:
return 1 # 终止条件
return n * factorial(n - 1) # 递归调用
print(factorial(5)) # 120(5×4×3×2×1)
执行过程:
factorial(5) = 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
▶ 示例:斐波那契数列(难度⭐⭐⭐)
def fibonacci(n):
"""返回第 n 个斐波那契数(从 0 开始)"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 打印前 10 个
for i in range(10):
print(fibonacci(i), end=" ")
# 输出:0 1 1 2 3 5 8 13 21 34
输出:
# 函数定义成功
7. 常见应用场景
- 默认参数:配置选项、超时时间、回调函数等场景。
- *args:数学聚合函数(sum、average)、日志格式化。
- *kwargs:函数包装器、配置合并、数据库查询参数构建。
- 递归:目录树遍历、JSON 多层嵌套解析、数学公式(阶乘/斐波那契)。
- 作用域:理解闭包(下一课)、理解变量生命周期。
❓ 常见问题
x = 10 在函数内会被当作"创建局部变量 x"。如果你确实要修改全局变量,用 global 声明。但这通常意味着你的设计可能需要改进。📖 小节
- 默认参数让函数更灵活,但不要用可变对象做默认值
- 关键字参数让你不必记住参数顺序,可以按名传参
*args打包多余位置参数为元组,**kwargs打包关键字参数为字典- 参数顺序:普通参数 →
*args→ 默认参数 →**kwargs - LEGB 规则决定变量的查找顺序:局部→外层→全局→内置
global在函数内修改全局变量,尽量少用- 递归是函数调用自身,需要终止条件和递归条件
📝 作业
-
基础题(难度⭐):写一个函数
multiply(*nums),接收任意多个数字,返回它们的乘积。如果没传参数,返回 0。 -
进阶题(难度⭐⭐):写一个函数
create_student(name, **scores),接收学生姓名和任意多门科目的成绩(关键字参数),计算并返回该学生的平均分。调用示例:create_student("Alice", 语文=85, 数学=92, 英语=78)。 -
挑战题(难度⭐⭐⭐):写一个递归函数
sum_digits(n),计算一个正整数各位数字之和。例如sum_digits(123)返回1+2+3=6。提示:对 10 取余得到个位数,对 10 整除去掉个位数。终止条件是 n < 10。