一:一切皆对象。
Python一切都是“对象”,包括“函数”。在很多其它语言中,函数只是一段可执行代码,但Python的“函数”是可以实例化的。
因此可以做到:
a=def
也可以:把函数作为对象传递到另一个函数里作为参数,也可以把一个函数作为另一个函数的返回结果。
def curve_pre():
def curve():
pass
return curve#返回一个函数
f=curve_pre()#函数赋给一个变量
二:什么是闭包?
首先需要知道,当需要用到一个变量值,但是在本级作用域没有定义,它就会在上一级作用域中去找。(普遍是这样)
闭包=函数+环境变量(内嵌函数定义时候的外部变量,但又不能是全局变量)
例子:
def curve_pre():
a=25
def curve(x):
return a*x*x
return curve
a=10
f=curve_pre()
print(f(2))
#f现在就是curve函数,但是它没有a的值,如果从上一级中去寻找,那应该打印40,但闭包会从“环境变量“中去找a的值。a=25就是闭包的环境变量。
结果:
100
大致明白了”环境变量“和“闭包”的意思了。
闭包只是在表现形式上跟函数类似,但实际上不是函数。
一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数 对象。环境变量取值被保存在函数对象的__closure__属性中。__closure__里包含了一个元组(tuple)。这个元组中的每个元素是cell类型的对象。
def curve_pre():
a=25
def curve(x):
return a*x*x
return curve
def curve_pre():
a=30
def curve(x):
return a*x*x
return curve#因为环境变量不同,这里是两个不同的闭包:要关注整体,除了看函数还要看环境变量。
以下是“闭包”的经典误区:
def f1():
a=10
def f2():
a=20#这个a是一个局部变量,不会影响到上面的a。
print(a)
print(a)
f2()
print(a)
f1()
思考一下上面代码的输出结果是什么?
结果:
10
20
10
再看一下下面这段代码:
def f1():
a=10
def f2():
a=20
return a
return f2
f=f1()
print(f)
print(f.__closure__)
结果:
<function f1.<locals>.f2 at 0x00DAA100>
None
我们发现f,即函数f1的返回,即f2,不是一个“闭包”。
f2和上面curve函数不同的地方在于f2也定义有a变量,是这个的原因吗?
我们实践一下就知道了:
def f1():
a=10
def f2():
#a=20
return a
return f2
f=f1()
print(f)
print(f.__closure__)
结果:
<function f1.<locals>.f2 at 0x00F4A100>
(<cell at 0x00F3A1F0: int object at 0x6A09E840>,)
激动人心!!现在f2是一个闭包了!__closure__属性再次回到它的身边。
然而,删掉f2内部对a的定义f2又成为一个闭包只是表面想象,实质的过程是:在f2内部有a的情况下,python认为a是一个局部变量,而不是一个环境变量,没有环境变量,闭包就不存在了;删掉a之后,a是一个环境变量了,闭包复活。
ps:查了一下资料,发现我对整体的观念还是不够深刻,在上面,我以为f2是闭包,但严格上来讲,f1才是一个闭包(从整体上考虑),也就是说,闭包包含:函数+内嵌函数+环境变量(被内嵌函数调用)。
三:如何创建一个闭包?
在Python中创建一个闭包可以归结为以下三点:
- 闭包函数必须有内嵌函数
- 内嵌函数需要引用该嵌套函数上一级中的变量
- 闭包函数必须返回内嵌函数
通过这三点,就可以创建一个闭包。(参考博客园Bluesky)
四:global与nonlocal
global 变量名#把该变量声明为全局变量(如果之前定义有同名的全局变量,那么这两个变量是同一个变量)
nonlocal 变量名#把该变量声明为“不是局部变量”,它会去找上一级变量。
五:闭包的环境变量意义
环境变量具有“保存现场”的功能,可以记忆上一次的状态。
老师:“经常用全局变量是一件很糟糕的事。值被改变都不知道。”
六:解决“旅行者问题”的普通法和闭包法。
普通法:
origin=0
def function(new):
global origin#如果没有这行,会报“local variable 'origin' referenced before assignment”
total=new+origin
origin=total
return total
print(function(2))
print(function(2))
print(function(2))
结果:
2
4
6
闭包法:
def function1(origin):
def function2(new):
nonlocal origin#没有这行也会报“变量在声明之前就被引用的错误”
total=origin+new
origin=total
return total
return function2
f=function1(0)
print(f(2))
print(f(2))
print(f(2))
结果:
2
4
6
七:查看闭包的环境变量。
一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中
闭包的函数的内置变量__closure__里包含了一个元组(tuple)。这个元组中的每个元素是cell类型的对象。
def function1(origin):
def function2(new):
nonlocal origin
total=origin+new
origin=total
return total
return function2
f=function1(0)
print(f.__closure__)#打印环境变量(cell类型)1
print(f.__closure__[0].cell_contents)#打印环境变量(用我看得懂的阿拉伯数字)2
print(f(2))#这一步之后环境变量变为2了 3
print(f.__closure__) 4
print(f.__closure__[0].cell_contents) 5
print(f(2)) 6
print(f.__closure__) 7
print(f.__closure__[0].cell_contents) 8
print(f(2)) 9
print(f.__closure__) 10
print(f.__closure__[0].cell_contents) 11
结果:
(<cell at 0x00AFA178: int object at 0x68E9E7A0>,) 1
0 2
2 3
(<cell at 0x00AFA178: int object at 0x68E9E7C0>,) 4
2 5
4 6
(<cell at 0x00AFA178: int object at 0x68E9E7E0>,) 7
4 8
6 9
(<cell at 0x00AFA178: int object at 0x68E9E800>,) 10
6 11
可以发现,闭包的环境变量在个程序之中达到了”保存现场“的效果。(这个值可以保存下来,)
八:一些补充。
“我们可以看到,line定义的隶属程序块中引用了高层级的变量b,但b信息存在于line的定义之外 (b的定义并不在line的隶属程序块中)。我们称b为line的环境变量。事实上,line作为line_conf的返回值时,line中已经包括b的取值(尽管b并不隶属于line)。
上面的代码将打印25,也就是说,line所参照的b值是函数对象定义时可供参考的b值,而不是使用时的b值。”(来源于博客园,忘记作者谁了,没有把源代码带过来,大致的意思就是环境变量是两个函数的夹心馅)
我的一些小结:闭包类似两个函数加上夹在两个函数中间的”环境变量“。类似于三明治。但是外面那个函数又是包裹着里面那个内嵌的函数。闭包返回来的是里面那个函数。在使用过程之中,可以添加代码使”馅料“保持更新的状态。
九:七月老师对函数式编程的看法。
python强调闭包的环境变量。
闭包机制:一个函数(内嵌的)调用另一个函数的局部变量。
环境变量长驻内存,可能造成“内存泄露”。JavaScript使用闭包过多网页可能会卡顿。
函数式编程不一定好,看自己适不适合。