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中使用不同编码读写txt文件详解
May 28 Python
Python判断Abundant Number的方法
Jun 15 Python
Python+微信接口实现运维报警
Aug 27 Python
python3实现TCP协议的简单服务器和客户端案例(分享)
Jun 14 Python
python清除字符串中间空格的实例讲解
May 11 Python
python 去除txt文本中的空格、数字、特定字母等方法
Jul 24 Python
Flask框架重定向,错误显示,Responses响应及Sessions会话操作示例
Aug 01 Python
使用matlab或python将txt文件转为excel表格
Nov 01 Python
Python参数传递机制传值和传引用原理详解
May 22 Python
Python基于BeautifulSoup爬取京东商品信息
Jun 01 Python
python基础入门之普通操作与函数(三)
Jun 13 Python
详解python网络进程
Jun 15 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
第三节--定义一个类
2006/11/16 PHP
php使用异或实现的加密解密实例
2013/09/04 PHP
php+mysqli使用面向对象方式更新数据库实例
2015/01/29 PHP
PHP5.6读写excel表格文件操作示例
2019/02/26 PHP
PHP实现财务审核通过后返现金额到客户的功能
2019/07/04 PHP
在修改准备发的批量美化select+可修改select时,在非IE下发现了几个问题
2007/01/09 Javascript
JavaScript 动态生成方法的例子
2009/07/22 Javascript
jquery 表单进行客户端验证demo
2009/08/24 Javascript
jquery.validate使用攻略 第一部
2010/07/01 Javascript
基于Jquery和html5的7款个性化地图插件
2015/11/17 Javascript
jQuery插件fullPage.js实现全屏滚动效果
2016/12/02 Javascript
整理关于Bootstrap模态弹出框的慕课笔记
2017/03/29 Javascript
xmlplus组件设计系列之树(Tree)(9)
2017/05/02 Javascript
nodejs中sleep功能实现暂停几秒的方法
2017/07/12 NodeJs
vue下跨域设置的相关介绍
2017/08/26 Javascript
jquery实现动态创建form并提交的方法示例
2019/05/27 jQuery
jQuery实现简易QQ聊天框
2020/02/10 jQuery
js实现登录时记住密码的方法分析
2020/04/05 Javascript
Element Breadcrumb 面包屑的使用方法
2020/07/26 Javascript
python3.3教程之模拟百度登陆代码分享
2014/01/16 Python
python将ip地址转换成整数的方法
2015/03/17 Python
python监控文件或目录变化
2016/06/07 Python
快速查询Python文档方法分享
2017/12/27 Python
Python读写及备份oracle数据库操作示例
2018/05/17 Python
详解Python计算机视觉 图像扭曲(仿射扭曲)
2019/03/27 Python
对Python _取log的几种方式小结
2019/07/25 Python
18个Python脚本可加速你的编码速度(提示和技巧)
2019/10/17 Python
python使用opencv resize图像不进行插值的操作
2020/07/05 Python
pycharm 实现复制一行的快捷键
2021/01/15 Python
CSS3 实现侧边栏展开收起动画
2014/12/22 HTML / CSS
详解Html5 Canvas画线有毛边解决方法
2018/03/01 HTML / CSS
Smallable意大利家庭概念店:设计师童装及家居装饰
2018/01/08 全球购物
乌鸦喝水教学反思
2014/02/07 职场文书
事务机电主管工作职责
2014/02/25 职场文书
节约粮食标语
2014/06/18 职场文书
作文之亲情600字
2019/09/23 职场文书