Skip to main content

Python 函数

Python 闭包函数

闭包是 Python 中高阶函数的一种特殊形式,广泛应用于函数式编程。本章节将从基础概念开始,逐步介绍闭包的实现、传入函数、返回函数以及nonlocal关键字的用法,并提供了完整的代码示例和注意事项。

1. 引入闭包概念

闭包(closure)是一个函数,它 “记住” 了定义时外部环境的变量。闭包通常由一个外层函数和嵌套的内层函数组成,外层函数返回内层函数,内层函数可以访问外层函数的变量。闭包常用于数据隐藏、状态保持和动态行为生成。

闭包的三个条件:

  1. 必须有一个内嵌函数。
  2. 内嵌函数必须引用外部函数中的变量。
  3. 外部函数返回内嵌函数。

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是外层函数,返回内层函数adderadder引用了外层函数的变量n,形成了闭包。add_5add_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变量。每次调用counter1counter2,它们维护独立的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 会认为countcounter函数的局部变量,导致错误。

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

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。