卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

    • Python函数参数是值传递还是引用传递?
    • 位置参数、关键字参数、*args和**kwargs怎么用?
    • Python默认参数为什么不能随便使用列表和字典?
    • Python中的匿名函数、map、filter和reduce怎么用?
    • Python的LEGB作用域是什么?
    • global和nonlocal有什么区别?
    • 什么是闭包?闭包中的变量为什么会出现延迟绑定?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python模块和包是什么?import是怎么工作的?
  • 面向对象

  • 进阶语法与语言机制

# 什么是闭包?闭包中的变量为什么会出现延迟绑定?

面试官问:"什么是闭包?下面这段代码为什么输出都是9?"

funcs = []
for i in range(3):
    funcs.append(lambda: i)

print([f() for f in funcs])  # [2, 2, 2]
1
2
3
4
5

很多人知道闭包可以"记住外层变量",但说不清楚延迟绑定的原因、闭包如何存储自由变量,以及如何避免循环中的闭包陷阱。

# 简要回答

闭包是一个函数对象,它可以访问其定义时所在作用域的变量,即使外层函数已经返回。这些被闭包引用的外层变量叫自由变量。

闭包中的变量是延迟绑定的。内层函数引用外层变量时,保存的是变量名,不是变量的当前值。调用闭包时,才会查找变量的当前值。

循环中创建闭包时,如果闭包引用循环变量,所有闭包共享同一个循环变量。 循环结束后,循环变量停留在最后一个值,所以所有闭包调用时看到的都是这个最后值。

解决方法:用默认参数立即捕获值,或用functools.partial。

# 详细回答

# 什么是闭包

闭包是一个函数对象,加上它引用的外层作用域变量:

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times_three = make_multiplier(3)
print(times_three(10))  # 30
1
2
3
4
5
6
7

multiplier引用了外层函数make_multiplier的局部变量n。make_multiplier返回后,times_three仍然可以访问n的值3。

# 自由变量与__closure__

被闭包引用的外层变量叫自由变量。Python把自由变量存储在函数对象的__closure__属性中:

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times_three = make_multiplier(3)
print(times_three.__closure__)  # (<cell at 0x...: int object at 0x...>,)
print(times_three.__closure__[0].cell_contents)  # 3
1
2
3
4
5
6
7
8

__closure__是一个元组,包含多个cell对象,每个cell保存一个自由变量的引用。

# 闭包中的变量是延迟绑定的

闭包保存的是变量名,不是变量的值:

def make_functions():
    funcs = []
    for i in range(3):
        funcs.append(lambda: i)
    return funcs

funcs = make_functions()
print([f() for f in funcs])  # [2, 2, 2]
1
2
3
4
5
6
7
8

为什么都是2?因为:

  1. 循环创建了3个lambda函数,每个都引用自由变量i。
  2. 3个函数共享同一个i,保存的是对i的引用,不是i的当前值。
  3. 循环结束后,i的值是2。
  4. 调用闭包时,查找i的当前值,都是2。

闭包延迟绑定与立即捕获对比

# 解决方法1:默认参数立即捕获

默认参数在函数定义时求值,可以立即捕获当前值:

def make_functions():
    funcs = []
    for i in range(3):
        funcs.append(lambda i=i: i)  # 默认参数立即捕获i的值
    return funcs

funcs = make_functions()
print([f() for f in funcs])  # [0, 1, 2]
1
2
3
4
5
6
7
8

lambda i=i中,右侧的i是循环变量,左侧的i是函数参数。每次循环时,默认参数i=i求值为当前循环变量的值。

# 解决方法2:functools.partial

from functools import partial

def make_functions():
    def printer(i):
        return i
    
    funcs = []
    for i in range(3):
        funcs.append(partial(printer, i))
    return funcs

funcs = make_functions()
print([f() for f in funcs])  # [0, 1, 2]
1
2
3
4
5
6
7
8
9
10
11
12
13

partial创建一个新函数,预填充参数。

# 解决方法3:用函数工厂

def make_functions():
    def make_lambda(i):
        return lambda: i
    
    funcs = []
    for i in range(3):
        funcs.append(make_lambda(i))
    return funcs

funcs = make_functions()
print([f() for f in funcs])  # [0, 1, 2]
1
2
3
4
5
6
7
8
9
10
11

每次调用make_lambda(i)创建新的局部作用域,lambda引用的是make_lambda的局部变量i,每个闭包有独立的i。

# 闭包修改外层变量

需要nonlocal声明:

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

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
1
2
3
4
5
6
7
8
9
10
11
12
13

闭包保存count的引用,nonlocal让increment可以修改这个外层变量。

# 多个闭包共享外层变量

def make_account():
    balance = 0
    
    def deposit(amount):
        nonlocal balance
        balance += amount
        return balance
    
    def withdraw(amount):
        nonlocal balance
        balance -= amount
        return balance
    
    return deposit, withdraw

deposit, withdraw = make_account()
print(deposit(100))   # 100
print(withdraw(30))   # 70
print(deposit(50))    # 120
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

deposit和withdraw共享同一个balance,通过闭包实现了状态封装。

# 知识拓展

Q1:闭包和嵌套函数是一回事吗?

不是。嵌套函数是定义在函数内部的函数。闭包是引用了外层作用域变量的嵌套函数。没有引用外层变量的嵌套函数不是闭包。

Q2:闭包会导致内存泄漏吗?

闭包保存外层变量的引用,只要闭包存在,外层变量就不会被垃圾回收。如果闭包生命周期很长,且引用了大量数据,可能导致内存占用增加。但这不是泄漏,只是合理的引用保持。

Q3:可以在闭包外查看自由变量的值吗?

可以通过__closure__:

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times_three = make_multiplier(3)
print(times_three.__closure__[0].cell_contents)  # 3
1
2
3
4
5
6
7

但这只用于调试,不应该在业务代码中依赖。

Q4:Python的装饰器是闭包吗?

通常是。装饰器返回的包装函数需要引用原函数,这个包装函数就是闭包:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("before")
        result = func(*args, **kwargs)  # 引用外层的func
        print("after")
        return result
    return wrapper
1
2
3
4
5
6
7

wrapper引用外层的func,是闭包。

Q5:JavaScript和Python的闭包行为一样吗?

基本一样,都存在延迟绑定陷阱。JavaScript中循环用var声明变量时,所有闭包共享同一个变量;用let或const时,每次循环创建新的块作用域。Python中推荐用默认参数或函数工厂解决。

Last Updated: 10/13/2026, 5:57:00 PM

← global和nonlocal有什么区别? Python模块和包是什么?import是怎么工作的? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪