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 能解释它,但人在实际写嵌套函数时容易搞混
global和nonlocal的使用场景。 - 闭包(Closure):外层函数返回内层函数,且内层函数捕获了外层变量——这涉及变量生命周期的延长和函数对象的内存模型。AI 可以生成闭包代码,但人需要理解"为什么能记住变量"。
- 可变对象作为默认参数:这是 Python 特有的"陷阱",其他语言(Java/JS)没有这个问题。
👤 人工干预率:8% | 人类重点关注
为什么需要人工干预:
- 作用域规则(LEGB):遇到
UnboundLocalError时,人需要理解是变量赋值导致的"遮蔽"问题 - 闭包的内存模型:闭包为什么能"记住"外层变量?本质是函数对象内部存储了捕获的变量引用
- 默认参数陷阱:
def func(lst=[])多次调用时lst会累积——这违反直觉
人类与 AI 协作建议:
- 让 AI 生成所有基本函数的代码示例
- 人工重点阅读下面的"作用域规则"和"闭包"部分,理解后可以让 AI 出几道题考自己
- 实际编码中遇到变量的意外引用时,优先检查 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.com2. 函数的参数
位置参数
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⚠️ 默认参数陷阱:默认值在函数定义时创建,不是每次调用时创建
pythondef 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) # ❌ TypeError3. 作用域(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: 100global 关键字
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()) # 34. 闭包(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.05. 递归
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()) # 10006. 匿名函数(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