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 相关文章推荐
Python ORM框架SQLAlchemy学习笔记之映射类使用实例和Session会话介绍
Jun 10 Python
python 实现求解字符串集的最长公共前缀方法
Jul 20 Python
python 将对象设置为可迭代的两种实现方法
Jan 21 Python
Python常见的pandas用法demo示例
Mar 16 Python
使用Python进行体育竞技分析(预测球队成绩)
May 16 Python
python求最大值,不使用内置函数的实现方法
Jul 09 Python
解决安装python3.7.4报错Can''t connect to HTTPS URL because the SSL module is not available
Jul 31 Python
python脚本实现音频m4a格式转成MP3格式的实例代码
Oct 09 Python
python 两个数据库postgresql对比
Oct 21 Python
MNIST数据集转化为二维图片的实现示例
Jan 10 Python
python中upper是做什么用的
Jul 20 Python
Python编写万花尺图案实例
Jan 03 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
PHP获取http请求的头信息实现步骤
2012/12/16 PHP
使用PHP编写的SVN类
2013/07/18 PHP
php实现读取手机客户端浏览器的类
2015/01/09 PHP
php使用PDO事务配合表格读取大量数据插入操作实现方法
2017/02/16 PHP
Laravel中服务提供者和门面模式的入门介绍
2017/11/06 PHP
php源码的安装方法和实例
2019/09/26 PHP
js宝典学习笔记(上)
2007/01/10 Javascript
Json对象与Json字符串互转(4种转换方式)
2013/03/27 Javascript
javascript制作坦克大战全纪录(1)
2014/11/27 Javascript
jquery 操作css样式、位置、尺寸方法汇总
2014/11/28 Javascript
基于jQuery Tipso插件实现消息提示框特效
2016/03/16 Javascript
Bootstrap模态框水平垂直居中与增加拖拽功能
2016/11/09 Javascript
jQuery插件FusionCharts实现的2D柱状图效果示例【附demo源码下载】
2017/03/06 Javascript
nodejs操作mysql实现增删改查的实例
2017/05/28 NodeJs
jquery点击回车键实现登录效果并默认焦点的方法
2018/03/09 jQuery
通过vue写一个瀑布流插件代码实例
2019/09/07 Javascript
highcharts.js数据绑定方式代码实例
2019/11/13 Javascript
小程序自定义模板实现吸顶功能
2020/01/08 Javascript
es6中Promise 对象基本功能与用法实例分析
2020/02/23 Javascript
简单了解Vue computed属性及watch区别
2020/07/10 Javascript
在vue中给后台接口传的值为数组的格式代码
2020/11/12 Javascript
跟老齐学Python之总结参数的传递
2014/10/10 Python
使用Python的Twisted框架构建非阻塞下载程序的实例教程
2016/05/25 Python
win8下python3.4安装和环境配置图文教程
2018/07/31 Python
Random 在 Python 中的使用方法
2018/08/09 Python
PyCharm+PySpark远程调试的环境配置的方法
2018/11/29 Python
Python qqbot 实现qq机器人的示例代码
2019/07/11 Python
python实现tail -f 功能
2020/01/17 Python
利用 PyCharm 实现本地代码和远端的实时同步功能
2020/03/23 Python
pycharm设置默认的UTF-8编码模式的方法详解
2020/06/01 Python
使用HTML和CSS3绘制基本卡通图案的示例分享
2015/11/06 HTML / CSS
临床护士自荐信
2014/01/31 职场文书
2015毕业生自我评价范文
2015/03/02 职场文书
2015年保险公司内勤工作总结
2015/05/23 职场文书
python中取整数的几种方法
2021/11/07 Python
Vue.js中v-bind指令的用法介绍
2022/03/13 Vue.js