Python 闭包(Closure)详解

3 阅读3分钟

一、认识闭包

1. 问题背景

在 Python 中,函数外部无法直接访问函数内部的局部变量,这是由 Python 的链式作用域决定的。

python

def f1():
    n = 999

print(n)  # 报错:name 'n' is not defined

2. 解决方案:函数嵌套

在函数内部定义嵌套函数,利用作用域特性实现外部访问。

python

def f1():
    n = 999
    def f2():
        print(n)
    return f2

result = f1()
result()  # 输出:999

二、闭包的概念

1. 通俗定义

闭包 = 内层嵌套函数 + 引用外层函数变量 + 作为返回值

2. 严谨定义(维基百科)

在支持函数嵌套的语言中,如果内部函数引用了外部函数的变量,就可能产生闭包。闭包可以在函数与一组 “私有” 变量之间建立关联,让这些变量在多次调用中保持持久。

3. 闭包两大核心作用

  1. 作为桥梁,读取函数内部的变量
  2. 将外层函数变量持久保存在内存中

三、闭包的用途

(一)读取函数内部的变量

隐藏变量、保持命名空间干净,同时对外提供访问入口。

python

def tag(tag_name):
    def add_tag(content):
        return "<{0}>{1}</{0}>".format(tag_name, content)
    return add_tag

add_tag_a = tag('a')
print(add_tag_a('Hello'))  # <a>Hello</a>

(二)让局部变量持久保存在内存中

普通函数执行完毕后局部变量会被垃圾回收,闭包可让变量长期留存。

python

def create(pos=[0, 0]):
    def go(direction, step):
        pos[0] += direction[0] * step
        pos[1] += direction[1] * step
        return pos
    return go

player = create()
print(player([1,0], 10))   # [10, 0]
print(player([0,1], 20))   # [10, 20]
print(player([-1,0], 10))  # [0, 20]

(三)用途总结

  • 既可以长久保存变量,又不会造成全局变量污染
  • 带参数装饰器的核心原理
  • 广泛用于爬虫、Web 开发、数据处理等场景

四、使用闭包的注意事项

1. 内存消耗问题

闭包会将变量常驻内存,滥用会导致内存泄漏、性能下降。解决办法:不使用时及时清理无用变量。

2. 适用场景

  • 功能简单、只有一个方法:优先用闭包,代码更简洁
  • 功能复杂、多方法:优先用面向对象(类)
  • 学习闭包核心价值:彻底理解装饰器

3. 不可变类型变量修改问题

内层函数默认不能直接修改外层 int/str/tuple 等不可变变量,会创建新局部变量。必须使用 nonlocal 声明。

python

def outer_fun():
    x = 0
    def inner_fun():
        nonlocal x
        x = 1
        print('inner x:', x)
    inner_fun()
    print('outer x:', x)

outer_fun()  # 输出 1 和 1

4. 禁止引用循环变量 / 后续变化变量

返回闭包时不要引用循环变量,否则所有函数都会取最终值

python

# 错误示例
def count():
    fs = []
    for i in range(1,4):
        def f():
            return i*i
        fs.append(f)
    return fs

f1,f2,f3 = count()
print(f1(),f2(),f3())  # 9 9 9

# 正确示例:用参数绑定当前值
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1,4):
        fs.append(f(i))
    return fs

f1,f2,f3 = count()
print(f1(),f2(),f3())  # 1 4 9

五、如何判断一个函数是闭包

查看函数的 closure 属性:

  • 闭包会返回一个由 cell 对象组成的元组
  • 通过 cell_contents 可查看捕获的变量

python

def add(x,y):
    def f(z):
        return x+y+z
    return f

d = add(5,6)
print(d.__closure__)
for cell in d.__closure__:
    print(cell.cell_contents)  # 输出 5、6

六、闭包核心总结

  1. 闭包 = 内层函数 + 引用外层变量 + 返回内层函数
  2. 作用:读内部变量、持久化保存变量
  3. 注意:内存消耗、nonlocal、避免循环变量
  4. 地位:Python 装饰器的基础,进阶必备