Python 闭包
什么是闭包?
在函数内部再定义一个函数,内部函数用到了外边函数的
变量,并且外部函数将内部函数的引用返回,那么这个函数以及用到的变量称之为闭包。
即:
当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。
eg1:
def outer():
a = 0#2.
def inner():#1.
sum = a+10
return sum
return inner#3.
f = outer()
print(f)
result = f()
print(result)
#检查一个函数是否为闭包__closure__函数,如果是闭包,返回cell,如果不是闭包,返回None
print(“检查闭包:”,outter().closure)#检查闭包: (<cell at 0x000001AB1BE37558: int object at 0x0000000063698110>,
<cell at 0x000001AB1BE37588: function object at 0x000001AB1BECAD08>)
**外函数返回了内函数的引用:
引用是什么?在python中一切都是对象,包括整型数据,函数,类等等其实都是对象。
当我们进行a=0的时候,实际上在内存当中有一个地方存了值0,然后用a这个变量名存了0所在内存位置的引用。大家可以把引用理解成地址。a只不过是一个变量名字,a里面存的是0这个数值所在的地址,就是a里面存了数值0的引用。
相同的道理,当我们在python中定义一个函数def outer(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个outer只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = outer, y = outer, 这样的操作就相当于,把outer里存的东西赋值给x和y,这样x 和y 都指向了outer函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的outer() ,调用的实际上根本就是一个函数,x、y和outer三个变量名存了同一个函数的引用。
有了上面的解释,我们可以继续说,返回内函数的引用是怎么回事了。对于闭包,在外函数outer中 最后return inner,我们在调用外函数 demo = outer() 的时候,outer返回了inner,inner是一个函数的引用,这个引用被存入了outer中。所以接下来我们再进行outer() 的时候,相当于运行了inner函数。
同时我们发现,一个函数,如果函数名后紧跟一对括号,相当于现在我就要调用这个函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
**外函数把临时变量绑定给内函数:
按照我们正常的认知,一个函数结束的时候,会把自己的临时变量都释放还给内存,之后变量都不存在了。一般情况下,确实是这样的。但是闭包是一个特别的情况。外部函数发现,自己的临时变量会在将来的内部函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量送给内函数绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
闭包中内函数修改外函数局部变量:
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1 global 声明全局变量 2 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
1. 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
2 .在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
eg2:
def test():
a = 6
def test2():
nonlocal a
a = a+7
return a
return test2
f2 = test()
print(f2)
r = f2()
print(r)
闭包作用:
1.装饰器 装饰器:在不改变原有功能的情况下增加新的功能。
2.面向对象 经历了上面的分析,我们发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。闭包也是实现面向对象的方法之一。在python当中虽然我们不这样用,在其他编程语言入比如JavaScript中,经常用闭包来实现面向对象编程。
'use strict';
// 定义数字0:
var zero = function (f) {
return function (x) {
return x;
}
};
// 定义数字1:
var one = function (f) {
return function (x) {
return f(x);
}
};
// 定义加法:
function add(n, m) {
return function (f) {
return function (x) {
return m(f)(n(f)(x));
}
}
}
3.实现单利模式!! 其实这也是装饰器的应用。
# 那这个闭包在实际的开发中有什么用呢?闭包主要是在函数式开发过程中使用。以下介绍两种闭包主要的用途。
用途1,当闭包执行完后,仍然能够保持住当前的运行环境。
比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。
我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),
我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。
棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction, step):
# 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
# 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
new_x = pos[0] + direction[0] * step
new_y = pos[1] + direction[1] * step
pos[0] = new_x
pos[1] = new_y
# 注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
return pos
return player
player = create() # 创建棋子player,起点为原点
print(player([1, 0], 10) ) # 向x轴正方向移动10步
print(player([0, 1], 20) ) # 向y轴正方向移动20步
print(player([-1, 0], 10)) # 向x轴负方向移动10步
“”"
输出为:
[10, 0]
[10, 20]
[0, 20]
“”"
**# 用途2,闭包可以根据外部作用域的局部变量来得到不同的结果,
这有点像一种类似配置功能的作用,我们可以修改外部的变量,
闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,
先要提取出这些特殊行。**
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter