python中的闭包函数


Posted in Python onFebruary 09, 2018

闭包函数初探

通常我们定义函数都是这样定义的

def foo():
 pass

其实在函数式编程中,函数里面还可以嵌套函数,如下面这样

def foo():
 print("hello world in foo")
 
 def bar():
 print("hello world in bar")

此时我们调用foo函数,执行结果会是什么样子的呢??

hello world in foo

结果如上所示,只会执行foo函数的第一层函数,bar函数是不会被执行的。为什么呢

实际上来说,不管函数写在哪个部分,那都只是定义了一个函数,只有这个函数被调用,函数内部的语句才会被执行

在上面的例子中,bar函数虽然在foo函数内部定义了,但是并没有被执行,所以bar函数是不会被执行的这样说来,定义在一个函数内部的函数就没什么作用了吗??其实不是这样的。

来看下面的例子,把bar函数作为一个值返回给foo函数,来看执行过程

def foo():
 print("hello world in foo")
 
 def bar():
 print("hello world in bar")
 return bar
f1=foo()
print(f1)

此时,由于bar函数作为一个返回值被返回给了foo,所以foo函数执行结果是有返回值的

此时定义一个变量f1来接收foo函数的执行返回结果,然后打印f1

返回的结果如下

hello world in foo
<function foo.<locals>.bar at 0x0000000002941A60>

可以看到首先打印了foo函数中定义的一个print语句,接着打印的是foo函数中包含的bar函数的内存地址

既然是一个函数的内存地址,当然可以加括号来执行这个函数

def foo():
 print("hello world in foo")
 def bar():
 print("hello world in bar")
 return bar
f1=foo()
f1()

此时,这段代码的执行结果为:

hello world in foo
hello world in bar

两个print语句都被打印出来了。

在上面的例子里,首先定义了一个函数foo,接着在foo函数内部又嵌套定义了一个函数bar,然后返回函数bar的函数名,这就是闭包函数的定义方式。

其实,闭包的定义就是一个函数内部又嵌套了一个函数

来看下面的这段代码

def foo():
 print("hello world in foo")
 name="python"
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1=foo()
 f1()

在上面的例子里,在外层函数中定义了一个变量name,然后在内层函数中打印这个变量name

此时执行上面的代码,在打印name这个变量的时候,会先在bar函数内部查找name这个变量,但是bar函数里面是没有name这个变量的,

此时根据python查找变量的LEGB法则,会到bar函数的外面一层去继续查找name这个变量,此时可以找到name这个变量

所以这里打印的foo函数中定义的name的值

执行上面的代码,打印结果如下

hello world in foo
python
hello world in bar

这里要记住很重要的一点就是:

内层函数引用了外层函数的局部变量

来分析下上面的例子中程序的执行过程:

首先运行foo函数,foo函数的执行结果是返回bar的函数名,此时又把foo函数的执行结果定义给了变量f1,
所以此时f1就等于bar这个函数的内存地址,然后f1加括号运行就表示运行了bar函数。
在执行bar函数的过程中,bar函数访问到了外层foo函数中定义的变量,这就是一个典型的闭包函数
那使用闭包函数有什么好处呢??在上面的例子里,f1的值是bar函数的内存地址,f1加括号运行就是在运行bar函数。

又由于f1是一个全局变量,这意味着可以在整个程序的任意位置都可以运行f1函数,此时再定义一个函数,在这个函数内部调用f1函数,

def foo():
 print("hello world in foo")
 name = "python"
 
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1 = foo()
 
 def func():
 name = "aaaaa"
 f1()
 func()

来分析一下程序的执行过程:

1.运行func函数,程序会先在内存中申请一块空间以保存name变量的值,然后运行f1函数,f1是在全局中定义的变量,所以一定可以找到f1函数的内存地址

2.f1加括号运行,就是在执行一个闭包函数,这个闭包函数内部引用了name这个变量

3.name这个变量在bar函数的外部已经定义了,所以在func函数内部调用f1函数,也就是bar函数时,其引用的变量依然是foo函数内部定义的name变量,而不是func函数内部定义的name变量,

4.因为f1函数的内部已经包含了name这个函数的值,所以就算在func函数内部也定义了name这个变量,程序执行的结果打印的依然是foo函数内部定义的name的值

程序执行结果

hello world in foo
python
hello world in bar

怎样验证一个函数是闭包函数

首先,闭包函数都有一个特有的属性:closure

在上面的例子里,打印f1的__closure__属性  

def foo():
 name = "python"
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 f1 = foo()
 print(f1.__closure__)

打印结果如下:

(<cell at 0x0000000001DF5708: str object at 0x0000000001E79688>,)

可以看到__closure__属性的打印结果是一个元组形式的,其值就是f1函数的外层函数作用域

此时可以调用__closure__返回的元组的元素的cell_contents方法打印出name变量的值

def foo():
 name = "python"
 
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1 = foo()
 print(f1.__closure__[0].cell_contents)

打印结果如下:

python

可以看到程序已经打印出name变量的值了

即然__closure__的返回结果是一个元组,那么这个元组中一定是可以包含多个值的,看下面的例子

在foo函数内部定义多个变量,然后在bar函数内部打印几个变量的值,

然后运行这个闭包函数,打印闭包函数的__closure__方法   

def foo():
 print("hello world in foo")
 name1 = "python1"
 name2 = "python2"
 name3 = "python3"
 name4 = "python4" 
 def bar():
  print(name1)
  print(name2)
  print(name3)
  print(name4)
  print("hello world in bar")
 return bar 
 f1 = foo()
 print(f1.__closure__)

程序执行结果

(<cell at 0x0000000002145708: str object at 0x00000000021C9260>, 
<cell at 0x0000000002145A08: str object at 0x00000000021C93B0>, 
<cell at 0x0000000002145768: str object at 0x000000000295BE30>, 
<cell at 0x0000000002145C18: str object at 0x0000000002963880>)

由于在foo函数内部定义了4个变量,而且在bar函数内部引用了这4个变量,所以打印这个闭包函数的__closure__方法,返回的元组中就有4个元素

现在可以分别打印返回的元组中的这4个字符串对象的值了   

def foo():
 name1 = "python1"
 name2 = "python2"
 name3 = "python3"
 name4 = "python4"
 
 def bar():
  print(name1)
  print(name2)
  print(name3)
  print(name4)
  print("hello world in bar")
 return bar 
 f1 = foo()
 print(f1.__closure__[0].cell_contents)
 print(f1.__closure__[1].cell_contents)
 print(f1.__closure__[2].cell_contents)
 print(f1.__closure__[3].cell_contents)

程序执行结果

python1
python2
python3
python4

那么现在还剩下最后一个问题了,那就是闭包函数的内层函数一定要返回吗??

来看下面一个例子

def foo():
 name = "python1" 
 def bar():
  print(name)
 print(bar.__closure__) 
 foo()

定义了一个嵌套函数,然后这个嵌套函数的内层函数没有被返回,而是直接打印内层函数的__closure__方法,然后直接调用外层函数。

程序执行结果

(<cell at 0x0000000002155708: str object at 0x00000000021D9688>,)

依然打印出了内层函数的引用的变量对象

这说明闭包函数的内层函数还一定要返回

闭包函数的内层函数可以调用全局变量吗??

把外层函数内部定义的变量改为全局变量,然后在内层函数中引用这个变量

name = "python1"
 def foo():
 def bar():
  print(name) 
 print(bar.__closure__)
 f=foo()
 print(f)

程序执行结果

None
None

可以看到,程序的执行结果是两个None,嵌套函数的内层函数的__closure__函数的值为None

这说明foo函数的内层嵌套函数bar调用的全局变量没有成功,所以上面的例子不是一个闭包函数

关于闭包函数的一些总结:

闭包的定义为:

    在函数内部定义的函数,称为内部函数
    内部函数调用了外部函数的局部变量
    即使内部函数返回了,还是可以使用局部变量
    通常闭包函数的内层函数都要被返回给外部函数
    闭包函数的外部函数可以在任何地方被调用,而不再受函数定义时层级的限制

闭包函数的作用

1.闭包函数自带函数作用域

正常意义上的函数,在函数执行过程中查找变量的顺序是一层一层向外找,符合LEGB(Local->Enclose->Global->Built in)法则的,

但是对闭包函数来说,查找变量只会找内部函数外面的那一层,因为闭包函数本身就自带一层作用域,这样才符合"闭包"两个字的意思

2.延迟计算(也叫惰性计算)

看下面的例子

def func():
 name="python"
 def bar():
  print(name)
 return bar 
 f=func()
 print(f.__closure)

在上面的例子里,执行foo()函数的返回结果是一个包含自带的某种状态的函数,实际上这个函数并没有执行,

以后想执行这个自带状态的函数时,把func()返回结果所赋值的那个变量加括号就可以执行了,

3.要想让一个函数始终保持一种状态,就可以使用闭包

例子:

name="python" 
 def func():
 print("I like %s" % name) 
 func()

上面的代码执行结果会打印一行:"I like python"

但是我们知道,在不同的地方调用func函数,打印的结果很大可能是不一样的

那么如果我想不管在什么地方调用func函数,打印的结果都是"I like python"时,

就可以使用闭包了。

def func1(): 
 name="python"
 def func():
  print("I like %s" % name)
 return func
 func=func1()
 func()

如上图所示,在func函数外面再包含一层函数func1,执行func1函数,再把func1函数的返回结果赋值给func这个变量

此时func就是一个闭包函数了,把func函数加括号就可以执行了

而且我们一定知道,此时func函数的执行结果一定会打印"I like python"这句话,而且不管func函数在程序的哪个位置被调用,执行结果都是一样的

Python 相关文章推荐
Python中解析JSON并同时进行自定义编码处理实例
Feb 08 Python
Python实现简单状态框架的方法
Mar 19 Python
Windows下实现Python2和Python3两个版共存的方法
Jun 12 Python
Python 爬虫图片简单实现
Jun 01 Python
django在接受post请求时显示403forbidden实例解析
Jan 25 Python
pandas实现选取特定索引的行
Apr 20 Python
深入理解python中sort()与sorted()的区别
Aug 29 Python
pandas 根据列的值选取所有行的示例
Nov 07 Python
Python3.6.2调用ffmpeg的方法
Jan 10 Python
Python 动态导入对象,importlib.import_module()的使用方法
Aug 28 Python
关于Keras模型可视化教程及关键问题的解决
Jan 24 Python
Django如何批量创建Model
Sep 01 Python
基于Python socket的端口扫描程序实例代码
Feb 09 #Python
利用python 更新ssh 远程代码 操作远程服务器的实现代码
Feb 08 #Python
Tensorflow 利用tf.contrib.learn建立输入函数的方法
Feb 08 #Python
django数据库migrate失败的解决方法解析
Feb 08 #Python
python使用锁访问共享变量实例解析
Feb 08 #Python
Python异常对代码运行性能的影响实例解析
Feb 08 #Python
Python科学计算包numpy用法实例详解
Feb 08 #Python
You might like
PHP实现图片简单上传
2006/10/09 PHP
PHP中防止直接访问或查看或下载config.php文件的方法
2012/07/07 PHP
关于二级目录拖拽排序的实现(源码示例下载)
2013/04/26 PHP
wordpress自定义url参数实现路由功能的代码示例
2013/11/28 PHP
destoon调用企业会员公司形象图片的实现方法
2014/08/21 PHP
使用Zookeeper分布式部署PHP应用程序
2019/03/15 PHP
Javascript 继承机制实例
2009/08/12 Javascript
JavaScript在XHTML中的用法详解
2013/04/11 Javascript
PHP结合jQuery实现的评论顶、踩功能
2015/07/22 Javascript
js控制li的隐藏和显示实例代码
2016/10/15 Javascript
jquery与ajax获取特殊字符实例详解
2017/01/08 Javascript
Angular directive递归实现目录树结构代码实例
2017/05/05 Javascript
Angular2使用vscode断点调试ts文件的方法
2017/12/13 Javascript
Node.js中的child_process模块详解
2018/06/08 Javascript
ES6 fetch函数与后台交互实现
2018/11/14 Javascript
vue-router 起步步骤详解
2019/03/26 Javascript
Vue匿名插槽与作用域插槽的合并和覆盖行为
2019/04/22 Javascript
详解JavaScript实现动态的轮播图效果
2019/04/29 Javascript
基于vue如何发布一个npm包的方法步骤
2019/05/15 Javascript
深入浅析ng-bootstrap 组件集中 tabset 组件的实现分析
2019/07/19 Javascript
vue+element项目中过滤输入框特殊字符小结
2019/08/07 Javascript
JavaScript 实现自己的安卓手机自动化工具脚本(推荐)
2020/05/13 Javascript
CentOS中使用virtualenv搭建python3环境
2015/06/08 Python
分享Python开发中要注意的十个小贴士
2016/08/30 Python
python实现逻辑回归的方法示例
2017/05/02 Python
实例讲解Python爬取网页数据
2018/07/08 Python
Python2包含中文报错的解决方法
2018/07/09 Python
pandas-resample按时间聚合实例
2019/12/27 Python
PyQt5实现仿QQ贴边隐藏功能的实例代码
2020/05/24 Python
Python Opencv轮廓常用操作代码实例解析
2020/09/01 Python
CSS3的resize属性使用初探
2015/09/27 HTML / CSS
使用CSS3的box-sizing属性解决div宽高被内边距撑开的问题
2016/06/28 HTML / CSS
澳大利亚男士西服品牌:M.J.Bale
2018/02/06 全球购物
电大奖学金获奖感言
2014/08/14 职场文书
2014年政风行风工作总结
2014/11/22 职场文书
mysql5.7的安装及Navicate长久免费使用的实现过程
2021/11/17 MySQL