Python 闭包函数
闭包是 Python 中高阶函数的一种特殊形式,广泛应用于函数式编程。本章节将从基础概念开始,逐步介绍闭包的实现、传入函数、返回函数以及nonlocal关键字的用法,并提供了完整的代码示例和注意事项。
1. 引入闭包概念
闭包(closure)是一个函数,它 “记住” 了定义时外部环境的变量。闭包通常由一个外层函数和嵌套的内层函数组成,外层函数返回内层函数,内层函数可以访问外层函数的变量。闭包常用于数据隐藏、状态保持和动态行为生成。
闭包的三个条件:
- 必须有一个内嵌函数。
- 内嵌函数必须引用外部函数中的变量。
- 外部函数返回内嵌函数。
2. 返回函数
闭包的核心是外部函数返回一个内部函数,内部函数保留对外层函数变量的引用。
示例:创建加法器闭包
def make_adder(n):
def adder(x):
return x + n
return adder
add_5 = make_adder(5)
add_10 = make_adder(10)
print(add_5(3)) # 输出: 8
print(add_10(3)) # 输出: 13
在这个例子中,make_adder
是外层函数,返回内层函数adder
。adder
引用了外层函数的变量n
,形成了闭包。add_5
和add_10
各自 “记住” 了不同的n
值。
3. nonlocal 关键字
在闭包中,如果内层函数需要修改外层函数的变量,必须使用nonlocal
关键字声明变量,否则 Python 会将其视为内层函数的局部变量。nonlocal
用于访问外层函数的变量,而非全局变量。
示例:计数器闭包
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter1 = make_counter()
counter2 = make_counter()
print(counter1()) # 输出: 1
print(counter1()) # 输出: 2
print(counter2()) # 输出: 1
在这个例子中,counter
函数使用nonlocal count
修改外层函数的count
变量。每次调用counter1
或counter2
,它们维护独立的count
状态。
示例:无 nonlocal
的错误情况
def make_counter():
count = 0
def counter():
count = count + 1 # 错误:未声明nonlocal,count被视为局部变量
return count
return counter
counter = make_counter()
# print(counter()) # 抛出UnboundLocalError
如果不使用nonlocal
,Python 会认为count
是counter
函数的局部变量,导致错误。
4. 闭包的实际应用
闭包常用于:
- 数据隐藏:通过闭包封装变量,防止外部直接访问。
- 状态保持:如计数器或配置生成器。
- 装饰器:闭包是 Python 装饰器的基础(后面详细介绍)。
示例:日志记录闭包
def make_logger(prefix):
def log(message):
return f"[{prefix}] {message}"
return log
error_log = make_logger("ERROR")
info_log = make_logger("INFO")
print(error_log("File not found")) # 输出: [ERROR] File not found
print(info_log("System started")) # 输出: [INFO] System started
make_logger
返回log
函数,log
记住prefix
并生成格式化的日志消息。
5. 注意事项
- 变量作用域:确保正确使用
nonlocal
修改外层变量,避免与全局变量混淆。 - 内存管理:闭包会保留外层函数变量的引用,可能导致内存占用增加,尤其是循环中创建闭包时。
- 可读性:闭包逻辑复杂时,添加文档字符串或注释提高代码可读性。
- 避免可变默认参数:外层函数的默认参数应为不可变类型,避免共享状态。
示例:避免可变默认参数
def make_list_appender():
lst = []
def append_item(item):
lst.append(item)
return lst
return append_item
appender1 = make_list_appender()
appender2 = make_list_appender()
print(appender1(1)) # 输出: [1]
print(appender2(2)) # 输出: [2]
正确实现中,lst
在每次调用make_list_appender
时重新创建。如果使用默认参数(如lst=[]
),会导致所有闭包共享同一个列表。
示例:错误的默认参数闭包
def make_list_appender(lst=[]):
def append_item(item):
lst.append(item)
return lst
return append_item
appender1 = make_list_appender()
appender2 = make_list_appender()
print(appender1(1)) # 输出: [1]
print(appender2(2)) # 输出: [1, 2],共享了lst
6. 闭包与循环的陷阱
在循环中创建闭包时,注意变量捕获行为。Python闭包捕获的是变量的引用,而不是值。
示例:循环中的闭包陷阱
def make_functions():
funcs = []
for i in range(3):
def func():
return i
funcs.append(func)
return funcs
funcs = make_functions()
print([f() for f in funcs]) # 输出: [2, 2, 2]
修复方法:使用参数绑定
def make_functions():
funcs = []
for i in range(3):
def func(x=i):
return x
funcs.append(func)
return funcs
funcs = make_functions()
print([f() for f in funcs]) # 输出: [0, 1, 2]
通过默认参数x=i
,每次循环绑定当前值,避免引用最终的i
。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。