目录
在下面的实例中,我们定义一个测试函数,它读取两个变量的值:一个是局部变量a的值,是函数的参数,一个是变量b的值,这个函数没有定义它.
一个函数,读取一个局部变量和一个全局变量.
出现错误并不奇怪,在上面的例子中,如果先给全局变量b赋值,也就是定义它,然后再调用f1,那它就不会出错.
下面看一个可能让你意想不到的例子,前两行代码与上述例子一样,然后为b赋值,再打印它的值,可是在赋值之前,第二个print失败了.
注意:首先输出了3,这表明print(a)语句执行了,但是第二个print语句没有执行,一开始我个人觉得会打印6出来,因为有个全局变量b,而且是在print(b)之后为局部变量b赋值的,可事实是,python编译函数时,它判断b是局部变量,因为在函数内部给它赋值了.生成的字节码证实了这种判断,python会尝试从本地环境中获取b,而后调用f2(3)时,f2的定义体会获取并打印局部变量a的值,但是尝试获取局部变量b的值时,发现b没有绑定值.
如果在函数中赋值,想让解释器把b当作全局变量,要使用global声明:
在python中的作用域规则只需要记住LEGB规则即可,L->local,代表局部作用域,E->Enclosed,代表嵌套函数的外部函数作用域,G->global,代表全局作用域,B->Built in代表的是内置函数的关键字.
其中,局部作用域是最高的级别,当L和G冲突时,是优先认为是局部变量,除非你使用关键字global声明,当L和E冲突时,也是优先认定是局部变量,就是内部嵌套函数的变量,除非你使用关键字nonlocal声明,该变量就是嵌套函数外部函数作用域的变量.为了形象记忆,可以记为LEG+B,腿你不爱吗(-_-)
可以理解为工厂函数,来料加工.
但是有一种方法除外,那就是在函数的内部,再定义一个函数。
- def f1():
- n=999
- def f2():
- print(n)
-
- return f2
-
- result = f1()
- result()
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是开头说到的,Python语言特有的作用域搜索顺序。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗?
- def f1():
- n=999
- def f2():
- print(n)
-
- return f2
-
- result = f1()
- result()
上一部分代码中的f2函数,就是闭包。
在上面的实例中,有一个外层函数的局部变量 n,有一个内层函数 f2,f2 里面可以访问到 n 变量,那这f2就是一个闭包。
下面再看一下维基百科的严谨定义:
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性,(记忆功能)
上面这段话实际上解释了闭包的一个定义和两个作用:
支持将函数当成对象使用的编程语言,一般都支持闭包。比如Python, JavaScript。
python中一切皆是对象.
闭包可以用在许多地方。
维基百科的定义中已经提到的它的两个用处:① 可以读取函数内部的变量,②让这些变量的值始终保持在内存中。
在第一部分中,我们讲到,有时候会为了保证命名空间的干净而把一些变量隐藏到函数内部,作为局部变量。但是由于Python中作用域的搜索顺序,函数内的变量不会被函数外的代码读取到。
如果这时候想要函数外部的代码能够读取函数内部的变量,那么就可以使用闭包。
闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。
其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。
怎么来理解这句话呢?一般来说,函数内部的局部变量在这个函数运行完以后,就会被Python的垃圾回收机制从内存中清除掉。如果我们希望这个局部变量能够长久的保存在内存中,那么就可以用闭包来实现这个功能。
以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 这里需要说明的是,每次运动的起点都是上次运动结束的终点。(小甲鱼用的是乌龟运动的例子)
- def create(pos=[0,0]):
-
- def go(direction, step):
- new_x = pos[0]+direction[0]*step
- new_y = pos[1]+direction[1]*step
-
- pos[0] = new_x
- pos[1] = new_y
-
- return pos
-
-
- return go
-
- player = create()
- print(player([1,0],10))
- print(player([0,1],20))
- print(player([-1,0],10))
在这段代码中,player实际上就是闭包go函数的一个实例对象。
它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],,第三次是反方向沿X轴退了10来到[0, 20]。
这证明了,函数create中的局部变量pos一直保存在内存中,并没有在create调用后被自动清除。
为什么会这样呢?原因就在于create是go的父函数,而go被赋给了一个全局变量,这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这个时候,闭包使得函数的实例对象的内部变量,变得很像一个类的实例对象的属性,可以一直保存在内存中,并不断的对其进行运算。