lesson17:Python函数之递归、匿名函数与变量作用域
目录
引言
一、递归函数:用自身解构复杂问题
1. 递归的基本结构
2. 递归的典型应用场景
3. 递归的优缺点与优化
二、匿名函数:用lambda实现“一句话函数”
1. lambda与普通函数的区别
2. lambda的典型应用
3. lambda的局限性
三、变量作用域:理解LEGB规则
1、LEGB规则的深度解析
(1)Local(局部作用域)
(2)Enclosing(嵌套作用域)
(3)Global(全局作用域)
(4)Built-in(内置作用域)
2. 修改作用域的高级关键字:global与nonlocal
(1)global:声明全局变量
(2)nonlocal:声明嵌套作用域变量
3. 常见作用域陷阱
总结
引言
函数是Python编程的核心基石,它不仅实现了代码的模块化与复用,更通过高级特性如递归、匿名函数和灵活的作用域管理,赋予开发者简洁高效的问题解决能力。本文将深入探讨递归函数的逻辑与应用、匿名函数的简洁之美,以及变量作用域的底层规则,帮助你构建更优雅、更健壮的Python代码。
一、递归函数:用自身解构复杂问题
递归函数是指在函数体内直接或间接调用自身的函数。它的核心思想是将复杂问题分解为与原问题结构相似的子问题,通过逐步简化问题规模,最终达到可直接求解的“基线条件”。
1. 递归的基本结构
一个合法的递归函数必须包含两部分:
- 基线条件(Base Case):终止递归的条件,避免无限循环。
- 递归条件(Recursive Case):调用自身解决规模更小的子问题。
示例1:计算n的阶乘(n!)
阶乘的数学定义为:n! = n × (n-1) × ... × 1,且0! = 1。
def factorial(n):
# 基线条件:n=0时返回1
if n == 0:
return 1
# 递归条件:n! = n × (n-1)!
return n * factorial(n-1)print(factorial(5)) # 输出:120
2. 递归的典型应用场景
- 数学问题:斐波那契数列、汉诺塔问题、幂运算等。
- 数据结构遍历:树的深度优先搜索(DFS)、链表反转等。
- 分治算法:快速排序、归并排序等。
示例2:斐波那契数列(第n项)
斐波那契数列定义:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)。
def fibonacci(n):
if n <= 1: # 基线条件:n=0返回0,n=1返回1
return n
return fibonacci(n-1) + fibonacci(n-2) # 递归条件print(fibonacci(6)) # 输出:8(数列:0,1,1,2,3,5,8...)
3. 递归的优缺点与优化
- 优点:逻辑简洁,代码可读性高(如树结构遍历)。
- 缺点:
- 递归深度有限制(Python默认递归深度约为1000,超过会抛出
RecursionError
)。 - 重复计算问题(如斐波那契数列的递归实现存在大量重复计算)。
- 递归深度有限制(Python默认递归深度约为1000,超过会抛出
- 优化方案:
- 尾递归优化:将递归调用放在函数返回的最后一步(但Python解释器不支持尾递归优化)。
- 记忆化搜索:用字典缓存已计算结果,避免重复计算。
# 优化后的斐波那契(记忆化搜索) memo = {0: 0, 1: 1} def fibonacci_optimized(n): if n not in memo: memo[n] = fibonacci_optimized(n-1) + fibonacci_optimized(n-2) return memo[n]
二、匿名函数:用lambda实现“一句话函数”
匿名函数(lambda
)是一种无需定义函数名的简洁函数形式,适用于逻辑简单、仅需一行代码的场景。它的语法为:
lambda 参数列表: 表达式 # 表达式结果即返回值
1. lambda与普通函数的区别
特性 | lambda函数 | 普通函数(def定义) |
---|---|---|
命名 | 匿名,无需函数名 | 必须指定函数名 |
代码量 | 仅一行表达式 | 可包含多行代码和复杂逻辑 |
返回值 | 自动返回表达式结果 | 需显式使用return语句 |
使用场景 | 临时、简单逻辑 | 复杂逻辑、可复用场景 |
2. lambda的典型应用
-
作为高阶函数参数:配合
map()
、filter()
、sorted()
等函数实现简洁逻辑。# 示例:用sorted()对字典按值排序(lambda作为key参数) students = {"Alice": 90, "Bob": 85, "Charlie": 95} sorted_students = sorted(students.items(), key=lambda x: x[1], reverse=True) print(sorted_students) # 输出:[('Charlie', 95), ('Alice', 90), ('Bob', 85)]
-
简化条件判断:替代简单的
if-else
逻辑。# 示例:根据分数返回等级(A/B/C/D) get_grade = lambda score: "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 60 else "D" print(get_grade(88)) # 输出:B
-
闭包与回调函数:在函数式编程中作为轻量级回调。
3. lambda的局限性
- 仅支持单个表达式,无法包含循环、条件语句块(如
if-elif-else
需用三元表达式简化)。 - 逻辑复杂时可读性差,建议优先使用普通函数。
三、变量作用域:理解LEGB规则
变量作用域指变量的可访问范围。Python通过LEGB规则确定变量查找顺序,即:Local(局部)→ Enclosing(嵌套)→ Global(全局)→ Built-in(内置)。
1、LEGB规则的深度解析
LEGB规则(Local → Enclosing → Global → Built-in)是变量查找的“黄金法则”,但在实际场景中可能因复杂嵌套或关键字修饰而产生例外。
(1)Local(局部作用域)
函数内部定义的变量(包括参数)默认属于局部作用域,仅在函数执行期间有效。
关键特性:
- 函数参数视为局部变量,例如
def func(a): print(a)
中的a
。 - 局部变量与全局变量同名时,局部变量会“遮蔽”(Shadowing)全局变量,即优先使用局部定义。
示例:局部变量遮蔽全局变量
x = 100 # 全局变量
def func():
x = 200 # 局部变量,遮蔽全局x
print("局部x:", x) # 输出:局部x: 200
func()
print("全局x:", x) # 输出:全局x: 100(全局x未被修改)
(2)Enclosing(嵌套作用域)
当函数嵌套时,内层函数可以访问外层函数定义的变量(非全局变量),这些变量属于嵌套作用域。
注意:外层函数的变量默认不可被内层函数修改,除非使用nonlocal
关键字。
示例:未使用nonlocal的嵌套作用域
def outer():
x = 10
def inner():
x = 20 # 此处x为inner的局部变量,不影响outer的x
print("inner局部x:", x) # 输出:inner局部x: 20
inner()
print("outer嵌套x:", x) # 输出:outer嵌套x: 10(未被修改)outer()
(3)Global(全局作用域)
模块级定义的变量(函数外定义)属于全局作用域,可被模块内所有函数访问。若需在函数内修改全局变量,必须用global
声明。
常见误区:
- 仅读取全局变量时无需声明
global
,但修改时必须声明,否则会被视为局部变量。
示例:正确修改全局变量
x = 10
def func():
global x # 声明x为全局变量
x = 20 # 修改全局x
func()
print(x) # 输出:20(全局x被成功修改)
(4)Built-in(内置作用域)
Python内置的函数和常量(如len
、list
、True
)存储在内置命名空间,优先级最低。若用户定义的变量与内置名称冲突,会遮蔽内置对象。
风险示例:遮蔽内置函数
len = 10 # 全局变量遮蔽内置len()
print(len([1,2,3])) # 报错:TypeError: 'int' object is not callable
2. 修改作用域的高级关键字:global
与nonlocal
(1)global
:声明全局变量
- 作用:将函数内的变量绑定到全局命名空间。
- 适用场景:需在函数内修改模块级全局变量。
示例:全局变量的跨函数共享
count = 0 # 全局计数器def increment():
global count
count += 1def decrement():
global count
count -= 1increment()
increment()
decrement()
print(count) # 输出:1
(2)nonlocal
:声明嵌套作用域变量
- 作用:将内层函数的变量绑定到最近的外层非全局作用域(即嵌套作用域)。
- 适用场景:嵌套函数中,内层函数需修改外层函数的变量。
示例:用nonlocal
实现计数器工厂
def make_counter(init=0):
count = init # 外层函数变量(嵌套作用域)
def counter():
nonlocal count # 绑定到外层count
count += 1
return count
return countercounter1 = make_counter(10)
print(counter1()) # 输出:11
print(counter1()) # 输出:12counter2 = make_counter(100)
print(counter2()) # 输出:101(独立于counter1)
global
vs nonlocal
对比:
关键字 | 绑定目标 | 适用场景 |
---|---|---|
global | 全局命名空间 | 修改模块级全局变量 |
nonlocal | 最近的外层非全局作用域 | 嵌套函数中修改外层函数变量 |
3. 常见作用域陷阱
-
局部变量遮蔽全局变量:函数内定义与全局变量同名的局部变量时,局部变量会遮蔽全局变量。
x = 10 def func(): x = 20 # 局部变量x,不影响全局x print(x) # 输出:20 func() print(x) # 输出:10(全局x未变)
-
未声明
global
直接修改全局变量:会报错UnboundLocalError
。x = 10 def func(): x += 1 # 错误:未声明global,x被视为局部变量但未定义 func() # 报错:UnboundLocalError: local variable 'x' referenced before assignment
总结
- 递归函数通过“自调用”解构复杂问题,需注意基线条件与递归深度限制,必要时用记忆化优化性能。
- 匿名函数(lambda) 以简洁的“表达式”形式实现轻量级逻辑,适合作为高阶函数参数或临时回调。
- 变量作用域遵循LEGB规则,
global
和nonlocal
关键字可修改变量的默认作用域行为,避免作用域混淆是代码健壮性的关键。
掌握这些函数特性,不仅能提升代码的简洁性与可读性,更能深入理解Python的底层逻辑,为编写高效、优雅的程序奠定基础。