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简单实现子网掩码转换的方法
Apr 13 Python
python中利用await关键字如何等待Future对象完成详解
Sep 07 Python
解决Django模板无法使用perms变量问题的方法
Sep 10 Python
python监控键盘输入实例代码
Feb 09 Python
Python3.5 创建文件的简单实例
Apr 26 Python
python验证码识别教程之利用投影法、连通域法分割图片
Jun 04 Python
Python判断中文字符串是否相等的实例
Jul 06 Python
python使用 zip 同时迭代多个序列示例
Jul 06 Python
Python 用三行代码提取PDF表格数据
Oct 13 Python
sklearn-SVC实现与类参数详解
Dec 10 Python
python实现AdaBoost算法的示例
Oct 03 Python
python 办公自动化——基于pyqt5和openpyxl统计符合要求的名单
May 25 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
解析yahoo邮件用phpmailer发送的实例
2013/06/24 PHP
PHP 范围解析操作符(::)用法分析【访问静态成员和类常量】
2020/04/14 PHP
获取dom元素那些讨厌的位置封装代码
2010/06/23 Javascript
一个js拖拽的效果类和dom-drag.js浅析
2010/07/17 Javascript
jquery.hotkeys监听键盘按下事件keydown插件
2014/05/11 Javascript
详谈javascript中的cookie
2015/06/03 Javascript
JS中正则表达式全局匹配模式 /g用法详解
2017/04/01 Javascript
Node.js利用debug模块打印出调试日志的方法
2017/04/25 Javascript
JS实现队列的先进先出功能示例
2017/05/10 Javascript
详解Angular-cli生成组件修改css成less或sass的实例
2017/07/27 Javascript
AngularJS实现图片上传和预览功能的方法分析
2017/11/08 Javascript
JS实现的点击按钮图片上下滚动效果示例
2019/01/28 Javascript
vue实现行列转换的一种方法
2019/08/06 Javascript
Kettle中使用JavaScrip调用jar包对文件内容进行MD5加密的操作方法
2020/09/04 Javascript
element-ui中el-upload多文件一次性上传的实现
2020/12/02 Javascript
node koa2 ssr项目搭建的方法步骤
2020/12/11 Javascript
[46:53]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
[03:11]不朽宝藏三外观展示
2020/09/18 DOTA
利用Python爬取微博数据生成词云图片实例代码
2017/08/31 Python
Python使用pyh生成HTML文档的方法示例
2018/03/10 Python
numpy的文件存储.npy .npz 文件详解
2018/07/09 Python
Python设计模式之职责链模式原理与用法实例分析
2019/01/11 Python
Python 3.8 新功能来一波(大部分人都不知道)
2020/03/11 Python
解决Pycharm双击图标启动不了的问题(JetBrains全家桶通用)
2020/08/07 Python
Pycharm中如何关掉python console
2020/10/27 Python
html5的canvas元素使用方法介绍(画矩形、画折线、圆形)
2014/04/14 HTML / CSS
加拿大城市本地限时优惠:Buytopia.ca
2018/09/19 全球购物
C#如何判断当前用户是否输入某个域
2015/12/07 面试题
毕业生个人求职信范文分享
2014/01/05 职场文书
小学英语课后反思
2014/04/26 职场文书
2014年工程部工作总结
2014/11/25 职场文书
高考学习决心书
2015/02/04 职场文书
博士给导师的自荐信
2015/03/06 职场文书
办公室禁烟通知
2015/04/23 职场文书
MySQL 查询速度慢的原因
2021/05/25 MySQL
TV动画《史上最强大魔王转生为村民A》番宣CM公布
2022/04/01 日漫