Python: 参数进阶与作用域

函数基础学会了,这一课来点进阶技巧。参数有多少种写法?"变量作用域"是什么意思?递归又是什么?这些概念掌握后,你对函数的理解会上一个台阶。


1. 默认参数

给参数指定默认值,调用时可以不传:

▶ 示例:默认参数基本用法

PYTHON
def greet(name, greeting="你好"):
    print(f"{greeting},{name}!")

greet("Alice")              # 你好,Alice!
greet("Bob", "Hello")     # Hello,Bob!
greet("Charlie", "嗨")        # 嗨,Charlie!
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

(1) ⚠️ 默认参数的陷阱

默认参数的值只在函数定义时计算一次,而不是每次调用时。如果默认值是可变对象(如列表、字典),会有意想不到的行为:

▶ 示例:可变默认参数的陷阱

PYTHON
def add_item(item, items=[]):   # ❌ 危险!
    items.append(item)
    return items

print(add_item("苹果"))     # ['苹果']
print(add_item("香蕉"))     # ['苹果', '香蕉'] —— 不是 ['香蕉']!
print(add_item("橘子"))     # ['苹果', '香蕉', '橘子']
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

原因: items=[] 这个空列表在函数定义时创建了一次,之后的每次调用都在同一个列表上追加。

正确做法:None 作为默认值,在函数内部创建新列表:

▶ 示例:安全的默认参数写法

PYTHON
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("橘子"))     # ['橘子']
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 黄金法则: 默认参数永远用不可变值(None、数字、字符串、元组)。不要用列表、字典等可变对象做默认值。



2. 关键字参数

调用函数时,可以通过参数名来指定值,这样可以不按顺序传参:

▶ 示例:关键字参数调用

PYTHON
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, "成都")   ❌ 关键字参数不能在位置参数前面
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


3. *args:可变位置参数

如果你不确定函数会接收多少个参数,用 *args

▶ 示例:*args 实现求和函数

PYTHON
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——没有参数也可以
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

*args 把所有传入的位置参数打包成一个元组

▶ 示例:查看 args 中的参数

PYTHON
def show_args(*args):
    print(f"接收到 {len(args)} 个参数:{args}")
    for i, arg in enumerate(args, 1):
        print(f"  第{i}个:{arg}")

show_args("苹果", "香蕉", "橘子")
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功


4. **kwargs:可变关键字参数

**kwargs 接收任意多个关键字参数,打包成一个字典

▶ 示例:**kwargs 构建用户资料

PYTHON
def create_profile(**info):
    """创建用户资料,接受任意多的信息"""
    print("用户资料:")
    for key, value in info.items():
        print(f"  {key}:{value}")

create_profile(name="Alice", age=25, city="北京", job="工程师")
▶ 试一试

运行结果:

TEXT 📖 仅展示
用户资料:
  name:Alice
  age:25
  city:北京
  job:工程师

▶ 示例:综合使用 *args 和 **kwargs

PYTHON
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}
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 参数顺序必须固定: 普通参数 → *args → 默认参数 → **kwargs



5. 作用域:LEGB 规则

变量在函数的什么位置能访问?这由作用域决定。Python 有四个层级的作用域:

▶ 示例:LEGB 作用域嵌套

PYTHON
# 全局作用域
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
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功

LEGB 代表:

Python 按 L → E → G → B 的顺序查找变量。

(1) global 和 nonlocal

如果你想在函数内部修改全局变量,用 global

▶ 示例:使用 global 修改全局变量

PYTHON
count = 0

def increment():
    global count     # 声明要操作全局变量
    count += 1

increment()
increment()
print(count)         # 2
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
⚠️ 尽量少用 global 全局变量让代码变得难以理解和调试。函数应该通过参数接收输入,通过 return 返回输出,而不是偷偷修改全局变量。



6. 递归入门

递归就是函数调用自己。递归的核心是:把一个大问题拆成更小的同类问题。

▶ 示例:递归计算阶乘

PYTHON
def factorial(n):
    """计算 n 的阶乘(n!)"""
    if n <= 1:
        return 1          # 终止条件
    return n * factorial(n - 1)   # 递归调用

print(factorial(5))        # 120(5×4×3×2×1)
▶ 试一试

执行过程:

TEXT 📖 仅展示
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

▶ 示例:斐波那契数列(难度⭐⭐⭐)

PYTHON
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
▶ 试一试

输出:

TEXT 📖 仅展示
# 函数定义成功
💡 递归的两大要素:终止条件(不然会无限递归 → 栈溢出)。② 递归条件(每次调用都让问题更接近终止条件)。不是所有问题都适合递归——循环能解决的问题,优先用循环。递归适合树形结构、分治算法等场景。



7. 常见应用场景


❓ 常见问题

Q 递归和循环哪个好?
A 能用循环解决的问题,优先用循环。循环性能更好、不会栈溢出、更容易理解。递归适合两个场景:树形结构的遍历(目录、JSON)和数学定义本身就是递归的(阶乘、斐波那契)。递归写起来更简洁,但理解起来更难。
Q 为什么函数内可以直接读取全局变量但不能修改?
A 读取时,Python 按 LEGB 规则找到全局变量。但赋值时,Python 默认在局部作用域创建新变量——x = 10 在函数内会被当作"创建局部变量 x"。如果你确实要修改全局变量,用 global 声明。但这通常意味着你的设计可能需要改进。

📖 小节


📝 作业

  1. 基础题(难度⭐):写一个函数 multiply(*nums),接收任意多个数字,返回它们的乘积。如果没传参数,返回 0。

  2. 进阶题(难度⭐⭐):写一个函数 create_student(name, **scores),接收学生姓名和任意多门科目的成绩(关键字参数),计算并返回该学生的平均分。调用示例:create_student("Alice", 语文=85, 数学=92, 英语=78)

  3. 挑战题(难度⭐⭐⭐):写一个递归函数 sum_digits(n),计算一个正整数各位数字之和。例如 sum_digits(123) 返回 1+2+3=6提示:对 10 取余得到个位数,对 10 整除去掉个位数。终止条件是 n < 10。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏