纪录一下目前对闭包的个人浅见:
闭包的概念:
- 存在内外层函数嵌套的情况
- 内层函数引用了外层函数的变量或者参数
- 外层函数把内层的这个函数本身当作返回值进行返回,而不是返回内层函数产生的某个值
举个最简单的例子:
def somebody(name):
def something(what):
print(name.title() + " wants to " + what + "!!!")
return something
result0 = somebody("lily")
result1 = somebody("jackie")
result0("eat cake") # Lily wants to eat cake!!!
result1("do homework") # Jackie wants to do homework!!!
上面是一个最简单的闭包,外层函数somebody()内定义了内层函数something(),并且内层函数调用到了外层的name做了一个print的事情,然后外层函数返回的是内层函数something这个函数本身,而非something()。这里我个人理解为是返回的外层函数输入为某个值时,此时内层函数的地址,比如上面的result0接收的就是外层输入为lily时,内层函数something的地址,因此result0仍然是一个函数对象。
下面单步调试验证一下:
在外层输入为lily时,可以看到外层函数返回的something函数地址是xxxx30FAE8,即result0指向的也是xxxx30FAE8地址(xxxx表示前面一长串懒得打的部分)
当外层输入变成jackie时,可以看到此时返回的something函数地址变成xxxx33FAE8了,即result1指向xxxx33FAE8这个地址。
往下运行到result0("eat cake")时,可以发现内存地址跳到了之前xxxx30FAE8的地址,也就是说之前的内存并没有被释放掉,而是作为当时外层函数状态下返回的函数地址,因此直接跳到了print()那一行执行输出。
再看一个例子:
def test():
funcs = []
for i in range(1,4):
def test2():
print(i)
funcs.append(test2)
return funcs
newFuncs = test()
newFuncs[0]() # 3
newFuncs[1]() # 3
newFuncs[2]() # 3
为什么输出的是333而不是123呢?实际上,newFuncs在获得test()返回的函数地址后,包含的三个存储为列表形式的函数地址的确是指向三个不同内存地址的,但是这三个内存地址指向的不同状态下的三个test2()却指向了同一个i,即for循环的最后一次循环i=3。为什么都指向同一个i=3呢?这是因为,在外层test()中,每一次for循环都定义了一个新的test2(),但此时内层函数test2()内i的值却从未传入过,还只是一个变量标识而已。最后一次循环i的值是3,因此在newFuncs[0]()这一步时,进入相应的内存地址指向的print(i),这是内层函数开始引用i的值了,而此时i早已是3了,所以内层函数直接引用到了i=3这个结果。
为方便验证,在原来的代码中加入两行输出i的地址:
def test():
funcs = []
for i in range(1,4):
def test2():
print(i)
print(id(i)) # 内层函数引用的i的地址
funcs.append(test2)
print(id(i)) # for循环时i的地址
return funcs
newFuncs = test()
newFuncs[0]()
newFuncs[1]()
newFuncs[2]()
运行一下:
140730656088912
140730656088944
140730656088976
3
140730656088976
3
140730656088976
3
140730656088976
可以发现for循环时i的地址是变化的,而调用到内层函数引用i的值值,往外层寻找i只能寻找到最后一次for循环也就是i=3时的地址了。但是注意,尽管如此,外层函数每次return的函数地址也是不一样的。
那如何改才能使输出的i是123而不是333呢?
def test():
funcs = []
for i in range(1,4):
def test2(num):
def inner():
print(num)
return inner
funcs.append(test2(i))
return funcs
newFuncs = test()
newFuncs[0]() # 1
newFuncs[1]() # 2
newFuncs[2]() # 3
如此修改后,funcs.append(test2(i))添加的是test2(i)函数的返回值,也就是inner的地址。而每次test2(i)返回的时候可是带有输入i的,这样内层函数inner就知道自己要引用的外层变量是什么了。这样newFuncs列表里存储的每个地址指向的内层函数inner就可以print出当时引用到的外层变量i了。
总结一下,闭包返回的是函数地址(所以返回的时候没有括号!),而非函数对象。对于某一外层函数输入状态下的闭包返回函数,其地址指向的内层函数引用的所有外层、内层的变量和表达式都是当时状态下固定好了的。