作为一pythoner,对闭包肯定都不陌生。
计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。通俗点说闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式是在一个函数内创建另一个函数,被创建的函数可以访问到外部函数的局部变量。那么,为什么要在一个函数内定义另一个函数,让内部函数访问外部函数的局部变量?像有些语言,比如c语言,就没有闭包这种东西,可见闭包也并不是不可或缺的东西,哪怕在python里面,一切用到闭包的地方,其实都可以去掉闭包给出一个无闭包的等价实现。
比如装饰器,就使用了闭包。
def wrapper(func):
def inner(*arg, **kwarg):
ret = func(*arg, **kwarg)
return ret
return inner
@wrapper
def fn():
print("fn called")
fn()
下面是等价的去闭包的实现。
class wrapper:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
ret = self.func(*args, **kwargs)
return ret
@wrapper
def fn():
print("fn called")
fn()
由此可见,闭包并非必须,但是没有闭包,只能像上面那样,要用类去实现,麻烦很多。你可能会想,在一个函数内部定义一个函数,在函数执行期间生成一个函数,是不是会拖慢效率,其实不然,看一下wrapper函数的字节码,
2 0 LOAD_CLOSURE 0 (func)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object inner at 0x7fa992c25d40, file “”, line 2>)
6 LOAD_CONST 2 ('wrapper.<locals>.inner')
8 MAKE_FUNCTION 8 (closure)
10 STORE_FAST 1 (inner)
5 12 LOAD_FAST 1 (inner)
14 RETURN_VALUE
Disassembly of <code object inner at 0x7fa992c25d40, file "", line 2>:
3 0 LOAD_DEREF 0 (func)
2 LOAD_FAST 0 (arg)
4 BUILD_MAP 0
6 LOAD_FAST 1 (kwarg)
8 DICT_MERGE 1
10 CALL_FUNCTION_EX 1
12 STORE_FAST 2 (ret)
4 14 LOAD_FAST 2 (ret)
16 RETURN_VALUE
字节码里只是多了个MAKE_FUNCTION的执行,inner函数的code object是在源文件编译(生成pyc文件)阶段生成的,开销比较大的词法分析,语法分析和生成代码的动作都不是在执行wrapper函数时进行的,MAKE_FUNCTION无非就是把code object,函数名,再加上自由变量(外部函数的局部变量)等一起打包成一个函数对象。
所以闭包是一个很方便的语言特性,动态语言支持闭包的很多,javascript、PHP等都支持闭包。
闭包的应用场景有哪些,什么时候用到闭包?
装饰器是一个典型的应用场景。
还有一个应用场景,就是函数递归调用且各级调用要共享数据的情况下,如果不用闭包,只能把共享的数据对象作为参数传来传去,代码很难看。
作者另一篇文章,整数的拆分,给了两个实现,一个用类实现,没有用到闭包,一个用闭包实现,见 正整数的拆分算法。
下面给出一个既不用类,又不用闭包的普通函数的实现。
def intization(res, n, frm):
for i in range(frm, int(n / 2) + 1):
res.append(i)
yield from intization(res, n - i, i)
res.pop()
res.append(n)
yield res
res.pop()
res = []
for i in intization(res, 5, 1):
print(i)
如果把它作为一个公用的接口提供给项目组使用,会很难堪,使用者在调用函数时需要传一个空的list和一个正整数1,会感到很别扭,我也要跟使用者解释,为什么要这么传。好的实现,应该是在函数调用时候只传入被拆分的n,然后得到拆分的结果,上面的实现非常尴尬,会给人很菜鸟的感觉,使用闭包,就避免了这种尴尬。