Python基础:闭包
闭包是函数式编程里的关键概念,它由函数和其词法环境共同构成。
简单来讲,闭包是一个(内层)函数与其相关外部变量的组合
。
通俗理解:
闭包就是一个能「记住」自己出生环境的函数。
核心特性:
即便外部函数执行完毕,内部函数依旧能够访问和修改外部函数的变量。
专业解释:
变量的可见性由代码的 静态结构
决定,并非运行时的调用链。函数在定义时就确定了其作用域链。
简单理解:
变量能被使用的范围
(词法作用域),就像你写代码时给它 “划了一个圈”:
圈画在哪里(代码结构),变量就在哪里生效,永远不变。
和之后 “怎么跑程序”(比如函数被调用的顺序、位置)无关。
这样理解,就像 “出生地决定籍贯” 一样,一旦确定,终身不变~
本质定义:
在函数中被使用,但既不是该函数的参数,也不是其局部变量的变量。
闭包函数通过 __closure__
属性存储这些变量的引用。
类比理解:
闭包的典型结构为 嵌套函数
,也就是在一个函数内部定义另一个函数,并且内部函数引用了外部函数的变量。
示例如下:
def outer():
outer_var = "数据"
def inner():
print(outer_var)
return inner
closure = outer()
closure() # 输出:数据
解析:
外层函数 outer()
outer_var = "数据"
,这是一个 自由变量(即被内层函数引用但未在内层函数中定义的变量)。inner()
,该函数引用了外层变量 outer_var
。inner
(注意返回的是函数对象的 引用
,不能加括号)。闭包的形成
closure = outer()
执行后,outer()
结束,其作用域理论上应销毁。inner
引用了 outer_var
,Python 会通过闭包机制将 outer_var
绑定到 inner
的 __closure__
属性中,使其持久化。闭包的调用
closure()
执行时,仍能访问 outer_var
,输出 "数据"
。outer_var
的值)。自由变量的捕获
闭包通过 __closure__
属性存储自由变量(如 outer_var
),每个变量以 cell
对象形式保存,可通过 closure.__closure__[0].cell_contents
查看。
print(closure.__closure__)
# 输出: |
print(closure.__closure__[0].cell_contents)
# 输出:数据
作用域与生命周期
外层函数的局部变量通常会随函数结束而销毁,但闭包会延长其生命周期。
内层函数的作用域链包含外层函数的命名空间,形成 词法作用域(静态作用域),与调用位置无关,即函数定义时的作用域决定变量查找规则,而非调用时的环境。
修改自由变量
若需在闭包中修改外层变量,需使用 nonlocal
声明,否则会被视为创建新局部变量。
def test():
x = 1
def inner():
nonlocal x
x = 2
print(x)
return inner
test()() # 输出:2
multiplier(factor)
)。closure = None
)。def outer():
data = [1, 2, 3] # 外部变量
def inner():
print(data)
return inner
closure = outer() # 创建闭包
closure() # 输出: [1, 2, 3]
# 手动解除引用
closure = None # 此时闭包和外部变量可以被垃圾回收
for i
中的 i
),所有闭包会共享最终值。扩展知识
模块)def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter_func = counter() # 返回函数increment
print(counter_func()) # 1
print(counter_func()) # 2
def tool_factory(name):
def tool(action):
print(f"{name}执行:{action}")
return tool
hammer = tool_factory("锤子")
screwdriver = tool_factory("螺丝刀")
hammer("敲钉子") # 锤子执行:敲钉子
screwdriver("拧螺丝") # 螺丝刀执行:拧螺丝
闭包概念:
由函数和其词法环境构成,是函数与其相关外部变量的组合,能记住出生环境,内部函数可访问修改外部函数变量。
核心概念:
词法作用域:变量可见性由代码静态结构决定,函数定义时确定作用域链。
自由变量:函数中使用的非参数和局部变量,闭包函数通过__closure__
属性存储引用。
闭包结构:
典型为嵌套函数,内部函数引用外部函数变量,外部函数返回内部函数引用。
闭包三要素:
函数嵌套、内层函数引用外层变量、外层函数返回内层函数。
闭包特性:
自由变量捕获:通过__closure__
属性存储自由变量,以cell
对象形式保存。
作用域与生命周期:延长外层函数局部变量生命周期,内层函数作用域链包含外层命名空间。
修改自由变量:需用nonlocal
声明,否则视为创建新局部变量。
闭包
VS 闭包函数
)在闭包的概念中,闭包函数特指“内层函数”
,但它必须满足一个关键条件:
内层函数引用了外层函数作用域中的变量,并且外层函数将这个内层函数作为返回值输出。
而这个被返回的内层函数 + 其使用的外部变量的组合
才被称为闭包。
例如:
def outer(x):
# 自由变量x将被内层函数捕获,成为闭包的一部分
def inner(y):
# 此处inner是闭包函数
# 它引用了外层作用域中的变量x,满足闭包形成条件
return x + y
# 将闭包函数(inner)作为返回值,此时 闭包 尚未完全形成
return inner
# 创建 闭包 的时刻:inner函数与 x=10 的组合
# 此时闭包 = inner函数 + 被捕获的x=10
closure_func = outer(10)
# 验证闭包中的捕获变量
print(closure_func.__closure__[0].cell_contents) # 输出:10
# 调用闭包函数(closure function)
# 闭包函数执行时会同时使用:
# 1. 被捕获的外部变量(x=10)
# 2. 当前传入的参数(y=5)
print(closure_func(5)) # 输出15
在以上示例中:
闭包函数
:inner()
)闭包
:inner + 外部变量 x 的组合
)外层函数返回的是“闭包”而非内层函数本身?
“引用外层变量 + 被外层返回”
这两个条件时,它就被称为闭包函数
。在Python中,闭包直接引用循环变量会导致所有闭包共享循环变量的最终值,这是一个常见的易错点。
问题代码示例:
def create_closures():
closures = []
for i in range(3):
def closure():
return i * i # 直接引用循环变量i
closures.append(closure)
return closures
# 获取闭包列表
cl1, cl2, cl3 = create_closures()
# 调用闭包
print(cl1(), cl2(), cl3()) # 输出: 4 4 4(不是预期的0 1 4)
问题原因:
所有闭包共享同一个变量i
的引用,而i
在循环结束后值为 2(range(3)
生成0,1,2),因此所有闭包返回2*2=4
。
解决方法:传递循环变量为默认参数
通过将循环变量作为默认参数绑定到闭包中,可以捕获循环变量的当前值:
方法1:使用嵌套函数
def create_closures_fixed():
closures = []
for i in range(3):
def make_closure(j): # 嵌套函数接收当前i值
def closure():
return j * j # 绑定到嵌套函数的局部变量i
return closure
closures.append(make_closure(i)) # 立即传递当前i值
return closures
# 测试
cl1, cl2, cl3 = create_closures_fixed()
print(cl1(), cl2(), cl3()) # 输出: 0 1 4
方法2:直接使用默认参数
def create_closures_simple():
closures = []
for i in range(3):
def closure(j=i): # 将i作为默认参数绑定
return j * j
closures.append(closure)
return closures
# 测试
cl1, cl2, cl3 = create_closures_simple()
print(cl1(), cl2(), cl3()) # 输出: 0 1 4
关键点:
i
的副本。对比说明
方法 | 输出结果 | 原理 |
---|---|---|
直接引用循环变量 | 4, 4, 4 |
所有闭包共享同一个i 的引用,最终值为2。 |
嵌套函数或默认参数 | 0, 1, 4 |
每个闭包捕获循环变量的当前值,形成独立的绑定。 |
说明:若需修改闭包外的变量,需使用nonlocal
声明(Python 3+)或使用可变对象(如列表等可直接修改操作)。
funcs = [lambda x, y=i: x + y for i in range(3)]
(多选题)以下操作会报错的是( ) ❓
print(funcs)
print(funcs[0](1))
print(funcs(1)[1])
print(funcs[3](1))
答案 :
会报错的选项:C、D