Python中的闭包实例详解


Posted in Python onAugust 29, 2014

一般来说闭包这个概念在很多语言中都有涉及,本文主要谈谈python中的闭包定义及相关用法。Python中使用闭包主要是在进行函数式开发时使用。详情分析如下:

一、定义

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).这个定义是相对直白的,好理解的,不像其他定义那样学究味道十足(那些学究味道重的解释,在对一个名词的解释过程中又充满了一堆让人抓狂的其他陌生名词,不适合初学者)。下面举一个简单的例子来说明。

>>>def addx(x): 
>>> def adder(y): return x + y 
>>> return adder 
>>> c = addx(8) 
>>> type(c) 
<type 'function'> 
>>> c.__name__ 
'adder' 
>>> c(10) 
18

结合这段简单的代码和定义来说明闭包:
如果在一个内部函数里:adder(y)就是这个内部函数,
对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,
则这个内部函数adder就是一个闭包。

再稍微讲究一点的解释是,闭包=函数块+定义函数时的环境,adder就是函数块,x就是环境,当然这个环境可以有很多,不止一个简单的x。

二、使用闭包注意事项

1.闭包中是不能修改外部作用域的局部变量的

>>> def foo(): 
...  m = 0 
...  def foo1(): 
...   m = 1 
...   print m 
... 
...  print m 
...  foo1() 
...  print m 
...
>>> foo()
0
1
0

从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。

2.以下这段代码是在python中使用闭包时一段经典的错误代码

def foo(): 
 a = 1 
 def bar(): 
  a = a + 1 
  return a 
 return bar

这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作。但在实际使用时

>>> c = foo() 
>>> print c() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
 File "<stdin>", line 4, in bar 
UnboundLocalError: local variable 'a' referenced before assignment

这是因为在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号"="的左面,被python认为是bar()中的局部变量。再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。解决的方法很简单

def foo(): 
 a = [1] 
 def bar(): 
  a[0] = a[0] + 1 
  return a[0] 
 return bar

只要将a设定为一个容器就可以了。这样使用起来多少有点不爽,所以在python3以后,在a = a + 1 之前,使用语句nonlocal a就可以了,该语句显式的指定a不是闭包的局部变量。

3.还有一个容易产生错误的事例也经常被人在介绍python闭包时提起,我一直都没觉得这个错误和闭包有什么太大的关系,但是它倒是的确是在python函数式编程是容易犯的一个错误,我在这里也不妨介绍一下。先看下面这段代码

for i in range(3): 
 print i

在程序里面经常会出现这类的循环语句,Python的问题就在于,当循环结束以后,循环体中的临时变量i不会销毁,而是继续存在于执行环境中。还有一个python的现象是,python的函数只有在执行时,才会去找函数体里的变量的值。

flist = [] 
for i in range(3): 
 def foo(x): print x + i 
 flist.append(foo) 
for f in flist: 
 f(2)

可能有些人认为这段代码的执行结果应该是2,3,4.但是实际的结果是4,4,4。这是因为当把函数加入flist列表里时,python还没有给i赋值,只有当执行时,再去找i的值是什么,这时在第一个for循环结束以后,i的值是2,所以以上代码的执行结果是4,4,4.
解决方法也很简单,改写一下函数的定义就可以了。

for i in range(3): 
 def foo(x,y=i): print x + y 
 flist.append(foo)

三、作用

说了这么多,不免有人要问,那这个闭包在实际的开发中有什么用呢?闭包主要是在函数式开发过程中使用。以下介绍两种闭包主要的用途。

用途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

如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

filter = make_filter("pass")
filter_result = filter("result.txt")

以上两种使用场景,用面向对象也是可以很简单的实现的,但是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不同的功能,是很有帮助的。

相信本文所述对大家的Python程序设计有一定的借鉴价值。

Python 相关文章推荐
MySQLdb ImportError: libmysqlclient.so.18解决方法
Aug 21 Python
python实现批量下载新浪博客的方法
Jun 15 Python
Pyqt实现无边框窗口拖动以及窗口大小改变
Apr 19 Python
python中in在list和dict中查找效率的对比分析
May 04 Python
在Pycharm中修改文件默认打开方式的方法
Jan 17 Python
Python实现的列表排序、反转操作示例
Mar 13 Python
基于OpenCV python3实现证件照换背景的方法
Mar 22 Python
Python面向对象进阶学习
May 21 Python
windows下安装Python虚拟环境virtualenvwrapper-win
Jun 14 Python
Python3 全自动更新已安装的模块实现
Jan 06 Python
matplotlib.pyplot.matshow 矩阵可视化实例
Jun 16 Python
Python中的pprint模块
Nov 27 Python
python进阶教程之文本文件的读取和写入
Aug 29 #Python
Python中__call__用法实例
Aug 29 #Python
python进阶教程之词典、字典、dict
Aug 29 #Python
python字符串排序方法
Aug 29 #Python
python基础教程之对象和类的实际运用
Aug 29 #Python
python基础教程之面向对象的一些概念
Aug 29 #Python
初学Python实用技巧两则
Aug 29 #Python
You might like
德生S2000收音机更换“钕铁硼”全频扬声器
2021/03/02 无线电
php &amp;&amp; 逻辑与运算符使用说明
2010/03/04 PHP
php flv视频时间获取函数
2010/06/29 PHP
php下通过伪造http头破解防盗链的代码
2010/07/03 PHP
批量去除PHP文件中bom的PHP代码
2012/03/13 PHP
PHP递归复制、移动目录的自定义函数分享
2014/11/18 PHP
PHP多维数组元素操作类的方法
2016/11/14 PHP
javascript实现复选框选中属性
2015/03/25 Javascript
JavaScript小技巧整理
2015/12/30 Javascript
微信小程序 页面跳转和数据传递实例详解
2017/01/19 Javascript
微信小程序商品详情页的底部弹出框效果
2020/11/16 Javascript
浅谈webpack devtool里的7种SourceMap模式
2019/01/14 Javascript
JavaScript实现随机点名程序
2020/03/25 Javascript
微信jssdk踩坑之签名错误invalid signature
2020/05/19 Javascript
vue实现在进行增删改操作后刷新页面
2020/08/05 Javascript
python编程开发之日期操作实例分析
2015/11/13 Python
Python PyQt5标准对话框用法示例
2017/08/23 Python
python实现串口自动触发工作的示例
2019/07/02 Python
Python math库 ln(x)运算的实现及原理
2019/07/17 Python
python通过SSH登陆linux并操作的实现
2019/10/10 Python
PyTorch中 tensor.detach() 和 tensor.data 的区别详解
2020/01/06 Python
python 追踪except信息方式
2020/04/25 Python
python读取图像矩阵文件并转换为向量实例
2020/06/18 Python
Python 如何查找特定类型文件
2020/08/17 Python
python 实现图片批量压缩的示例
2020/12/18 Python
浏览器实现移动端高性能css3动画(开启gpu加速)
2013/12/23 HTML / CSS
Steve Madden官网:美国鞋类品牌
2017/01/29 全球购物
软件测试面试题
2015/10/21 面试题
高一历史教学反思
2014/01/13 职场文书
护理学应聘自荐书范文
2014/02/05 职场文书
葛优非诚勿扰搞笑征婚台词
2014/03/17 职场文书
职位说明书范文
2014/05/07 职场文书
公司副总经理岗位职责
2015/04/08 职场文书
Html5通过数据流方式播放视频的实现
2021/04/27 HTML / CSS
详解CSS开发过程中的20个快速提升技巧
2021/05/21 HTML / CSS
在SQL Server中使用 Try Catch 处理异常的示例详解
2022/07/15 SQL Server