作为一名数仓工程师,我平时使用 Python 的场景主要集中在脚本开发和数据处理。
直到有一次被问到“什么是闭包”时,我才意识到自己对这个概念的理解其实并不深入。
于是我整理了这篇文章,希望用清晰的语言和示例,带大家真正弄懂闭包的原理和应用。


一、什么是闭包

在 Python 中,闭包(Closure)是指:
一个函数(内部函数)定义在另一个函数(外部函数)内部,并且内部函数引用了外部函数的变量。
当外部函数返回内部函数时,这个内部函数就形成了闭包。闭包能够“记住”它被创建时所在的作用域环境。

这里的关键是“词法作用域”。
词法作用域是指函数在定义时(而不是运行时)决定变量可见性的规则。
闭包之所以能在外部函数执行完后仍然访问外部变量,就是因为它保存了定义时的作用域环境。


二、闭包的三要素

  1. 嵌套函数:在一个函数内部定义另一个函数
  2. 变量引用:内部函数引用了外部函数的变量
  3. 函数返回:外部函数返回内部函数

三、闭包的基本示例

def outer_function(outer_var):
    """外部函数"""
    def inner_function(inner_var):
        """内部函数 - 闭包"""
        return f"外部变量: {outer_var}, 内部变量: {inner_var}"
    return inner_function

closure = outer_function("外部值")
print(closure("内部值"))       # 输出: 外部变量: 外部值, 内部变量: 内部值
print(closure("另一个内部值"))  # 输出: 外部变量: 外部值, 内部变量: 另一个内部值

在这里插入图片描述

在这个例子中,inner_function 就是一个闭包,它记住了 outer_var 的值。
即使 outer_function 已经执行完毕,闭包依然能访问到这个外部变量。


四、闭包的内部机制

闭包在 Python 中其实是一种函数对象,它通过 __closure__ 属性保存被引用的变量。

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

c1 = counter()
c2 = counter()

print(c1())  # 1
print(c1())  # 2
print(c2())  # 1
print(c1())  # 3

print(c1.__closure__)
print([cell.cell_contents for cell in c1.__closure__])

在这里插入图片描述

__closure__ 是一个包含外部变量的元组。
通过它,我们可以看到闭包内部真正保存了哪些数据。

这里的 nonlocal 关键字用来声明 count 不是局部变量,从而允许在内部函数中修改外部函数作用域的变量。


五、循环陷阱(延迟绑定问题)

闭包的一个常见陷阱是“延迟绑定”。
看下面的例子:

def create_functions():
    functions = []
    for i in range(3):
        def func():
            return i
        functions.append(func)
    return functions

funcs = create_functions()
for f in funcs:
    print(f())  # 输出 2, 2, 2

在这里插入图片描述

问题的原因是闭包捕获的是变量的“引用”,而不是定义时的“值”。
循环结束后,i 的值是 2,所以所有闭包返回的都是 2。

解决方案:默认参数捕获值

def create_functions_fixed():
    functions = []
    for i in range(3):
        def func(i=i):
            return i
        functions.append(func)
    return functions

在这里插入图片描述
不细看真看不出区别,但是结果却完全不一样,这是因为闭包中变量的绑定时机的不同。

在第一个函数中:
闭包捕获的是“变量的引用”,而不是当时的值。
所以三个函数都指向同一个 i,最终它的值变成了 2。

在第二个函数中:
默认参数 i=i 会在函数定义时求值(定义那一刻执行赋值),
所以每次循环时,func 都拿到了不同的 i 值,形成了独立的绑定。

六、闭包的常见应用

1. 装饰器

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完毕")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(2, 3))

装饰器的本质其实就是闭包。


2. 配置函数

def create_multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

double = create_multiplier(2)
triple = create_multiplier(3)

print(double(5))
print(triple(5))

3. 数据隐藏与封装

def create_bank_account(initial_balance=0):
    balance = initial_balance

    def deposit(amount):
        nonlocal balance
        balance += amount
        return balance

    def withdraw(amount):
        nonlocal balance
        if amount <= balance:
            balance -= amount
            return balance
        else:
            return "余额不足"

    def get_balance():
        return balance

    return {
        'deposit': deposit,
        'withdraw': withdraw,
        'get_balance': get_balance
    }

account = create_bank_account(100)
print(account['get_balance']())
print(account )
print(account )

闭包可以让数据在函数外部不可见,从而实现类似“私有变量”的效果。


4. 回调函数

def create_event_handler(button_name):
    def handle_event(event):
        print(f"按钮 {button_name} 被点击,事件: {event}")
    return handle_event

button1 = create_event_handler("确定")
button2 = create_event_handler("取消")

button1("click")
button2("click")

七、闭包与内存管理

闭包会保持对外部变量的引用,如果引用的是大对象,可能会导致内存无法及时释放。

import sys

def test_memory():
    def outer():
        large_data = "x" * 1000000
        def inner():
            return len(large_data)
        return inner

    closure = outer()
    print(sys.getsizeof(closure))
    print([cell.cell_contents for cell in closure.__closure__])

    del outer
    print("闭包仍然可用:", closure())

test_memory()

需要注意:sys.getsizeof() 只能得到函数对象本身的大小,不包括被引用对象的内存。


八、闭包的优缺点

优点:

  1. 数据隐藏,防止外部修改
  2. 函数能记住状态,实现状态持久化
  3. 提高代码复用性
  4. 是装饰器等高级特性的基础

缺点:

  1. 可能造成内存占用
  2. 调试不直观
  3. 容易出现延迟绑定问题

九、总结

闭包是 Python 中非常有用的特性,它让函数具备了“记忆”能力。
掌握闭包的关键在于理解三点:

  1. 闭包由嵌套函数、变量引用、返回函数三部分组成
  2. 闭包保存的是变量的引用,而非值
  3. 装饰器、工厂函数、状态保持等场景都离不开闭包
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐