Skip to content

05 函数与作用域

📋 资料说明

  • 来源:课程 day04~day05
  • 笔记尚硅谷大模型技术之Python1.0.docx 第 6 章"函数"
  • 每日一考day04_每日一考.md(4题)、day05_每日一考.md(4题)
  • 视频:❌ 跳过(约 36 个视频:函数定义 → 参数 → 闭包 → 作用域 → 递归 → 匿名函数)

🟢 🤖 AI 替代率:92%

为什么是这个水平:

函数的基本定义、参数传递、返回值这些方面 AI 可以完美生成。但以下概念需要人类深入理解:

  • 作用域(LEGB 规则):Python 的变量查找顺序(Local → Enclosing → Global → Built-in)不是语法规则,而是语言设计决策。AI 能解释它,但人在实际写嵌套函数时容易搞混 globalnonlocal 的使用场景。
  • 闭包(Closure):外层函数返回内层函数,且内层函数捕获了外层变量——这涉及变量生命周期的延长函数对象的内存模型。AI 可以生成闭包代码,但人需要理解"为什么能记住变量"。
  • 可变对象作为默认参数:这是 Python 特有的"陷阱",其他语言(Java/JS)没有这个问题。

👤 人工干预率:8% | 人类重点关注

为什么需要人工干预:

  1. 作用域规则(LEGB):遇到 UnboundLocalError 时,人需要理解是变量赋值导致的"遮蔽"问题
  2. 闭包的内存模型:闭包为什么能"记住"外层变量?本质是函数对象内部存储了捕获的变量引用
  3. 默认参数陷阱def func(lst=[]) 多次调用时 lst 会累积——这违反直觉

人类与 AI 协作建议:

  1. 让 AI 生成所有基本函数的代码示例
  2. 人工重点阅读下面的"作用域规则"和"闭包"部分,理解后可以让 AI 出几道题考自己
  3. 实际编码中遇到变量的意外引用时,优先检查 LEGB 查找链路

1. 函数的定义与调用

python
# 基本定义
def greet(name):
    """向某人打招呼"""    # 函数说明文档
    return f"Hello, {name}"

# 调用
result = greet("Alice")
print(result)  # Hello, Alice

函数说明文档

python
def add(a, b):
    """
    计算两个数的和
    
    Parameters:
        a (int): 第一个数
        b (int): 第二个数
    
    Returns:
        int: 两数之和
    """
    return a + b

# 查看文档
print(add.__doc__)
help(add)

return 关键字

python
# 无 return → 返回 None
def do_nothing():
    pass

print(do_nothing())  # None

# 返回多个值(实为元组)
def get_user():
    return "Alice", 25, "alice@email.com"

name, age, email = get_user()
print(name, age, email)  # Alice 25 alice@email.com

2. 函数的参数

位置参数

python
def greet(name, greeting):
    print(f"{greeting}, {name}")

greet("Alice", "Hello")     # Hello, Alice
greet("Hello", "Alice")     # Alice, Hello(顺序错误!)

默认参数

python
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}")

greet("Alice")              # Hello, Alice
greet("Bob", "Hi")         # Hi, Bob

⚠️ 默认参数陷阱:默认值在函数定义时创建,不是每次调用时创建

python
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['a', 'b']  ← 同一个列表!

# 正确做法
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

关键字参数

python
def introduce(name, age, city):
    print(f"{name}, {age}岁, 来自{city}")

# 关键字参数可以不按顺序
introduce(city="北京", name="Alice", age=25)

不定长参数

python
# *args:接收任意数量的位置参数,打包为元组
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))       # 6
print(sum_all(1, 2, 3, 4, 5)) # 15

# **kwargs:接收任意数量的关键字参数,打包为字典
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="北京")
# name: Alice
# age: 25
# city: 北京

# 组合使用
def func(a, b, *args, **kwargs):
    print(a, b, args, kwargs)

func(1, 2, 3, 4, x=5, y=6)
# 1 2 (3, 4) {'x': 5, 'y': 6}

参数传递与解包

python
# 解包列表/元组为位置参数
def add(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
print(add(*numbers))  # 6

# 解包字典为关键字参数
def introduce(name, age):
    print(f"{name}, {age}岁")

info = {"name": "Alice", "age": 25}
introduce(**info)  # Alice, 25岁

参数传递限制

python
# 仅限位置参数(Python 3.8+,/ 之前的位置参数)
def func(a, b, /, c):
    print(a, b, c)

func(1, 2, 3)        # OK
func(1, 2, c=3)      # OK
# func(a=1, b=2, c=3)  # ❌ TypeError

# 仅限关键字参数(* 之后必须是关键字参数)
def func(a, b, *, c, d):
    print(a, b, c, d)

func(1, 2, c=3, d=4)  # OK
# func(1, 2, 3, 4)     # ❌ TypeError

3. 作用域(LEGB 规则)

Python 查找变量的顺序:Local → Enclosing → Global → Built-in

python
# 全局作用域
x = 100

def outer():
    # 外层函数作用域(Enclosing)
    x = 50
    
    def inner():
        # 局部作用域(Local)
        x = 10
        print(f"Local: {x}")
    
    inner()
    print(f"Enclosing: {x}")

outer()
print(f"Global: {x}")

# 输出:
# Local: 10
# Enclosing: 50
# Global: 100

global 关键字

python
count = 0

def increment():
    global count    # 声明使用的是全局变量
    count += 1

increment()
print(count)  # 1

如果没有 global,函数内的 count += 1 会认为 count 是局部变量,而它还没定义就使用,抛出 UnboundLocalError

nonlocal 关键字

python
def outer():
    count = 0
    
    def inner():
        nonlocal count  # 引用外层函数的变量
        count += 1
        return count
    
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

4. 闭包(Closure)

闭包 = 函数 + 捕获的外部变量。内层函数"记住"了外层函数的变量,即使外层函数已经返回。

python
def make_multiplier(n):
    def multiplier(x):
        return x * n     # n 来自外层函数
    return multiplier

# double 和 triple 都是闭包
double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

闭包的内存模型

make_multiplier(2) 被调用后:
  栈帧中 n=2
  返回 multiplier 函数对象
  → multiplier.__closure__[0].cell_contents == 2
  → 这个 cell 不会被 GC 回收,因为 multiplier 还在引用它

所以 double(5) 能访问到 n=2

闭包的应用

python
# 1. 计数器(代替全局变量)
def create_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

counter = create_counter()
print(counter())  # 1
print(counter())  # 2

# 2. 延迟计算
def make_averager():
    numbers = []
    def averager(n):
        numbers.append(n)
        return sum(numbers) / len(numbers)
    return averager

avg = make_averager()
print(avg(10))  # 10.0
print(avg(20))  # 15.0
print(avg(30))  # 20.0

5. 递归

python
def factorial(n):
    """计算 n!(递归版)"""
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 120

def fibonacci(n):
    """斐波那契数列(递归版,效率低)"""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 递归的限制:Python 有递归深度限制(默认 1000)
import sys
print(sys.getrecursionlimit())  # 1000

6. 匿名函数(lambda)

python
# lambda 参数: 表达式
square = lambda x: x ** 2
print(square(5))  # 25

# 常用于排序和筛选
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
students.sort(key=lambda s: s[1])  # 按成绩排序
print(students)  # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]

# 与 map/filter 配合
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))         # [1, 4, 9, 16, 25]
evens = list(filter(lambda x: x % 2 == 0, numbers))    # [2, 4]

Python vs Java/JS 差异

  • Python lambda 只能写单个表达式,不能写语句块
  • JS 箭头函数可以写多语句块
  • Java lambda 语法类似但类型检查更严格

练习题

练习 1:默认参数

定义 greet(name, message="Hello"),分别测试传入和不传入 message 参数的输出。

练习 2:不定长参数

定义 sum_all(*args),计算任意数量参数的和。

练习 3:闭包计数器

写一个闭包,每次调用返回递增的数字,从 1 开始。

练习 4:递归

使用递归实现 1 + 2 + ... + n 的计算。


参考答案

练习 1:

python
def greet(name, message="Hello"):
    print(f"{message}, {name}")

greet("Alice")        # Hello, Alice
greet("Bob", "Hi")   # Hi, Bob

练习 2:

python
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))        # 6
print(sum_all(10, 20, 30, 40)) # 100

练习 3:

python
def create_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

counter = create_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

练习 4:

python
def sum_n(n):
    if n <= 1:
        return n
    return n + sum_n(n - 1)

print(sum_n(100))  # 5050

OPC 超级个体实战指南