详解python中的闭包


Posted in Python onSeptember 07, 2020

闭包的概念

我们尝试从概念上去理解一下闭包。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部函数引用了外部函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用过程中,这些私有变量能够保持持久性。
用比较容易懂得人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。看下例子:

def make_printer(msg):
  def printer():
    print(msg) # 夹带私货(外部变量)
 
  return printer # 返回的是函数,带私货的函数
 
 
printer = make_printer("Foo!")
printer()

支持将函数当成对象使用的编程语言,一般都支持闭包。比如python,JavaScript。

如何理解闭包

闭包存在有什么意义呢?为什么需要闭包
我个人认为,闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口编程。
接口定义了一套对方法签名的约束法则。

def tag(tag_name):
  def add_tag(content):
    return "<{0}>{1}</{0}>".format(tag_name, content)
  return add_tag
 
content = "Hello"
 
add_tag = tag('a')
print(add_tag(content)) # <a>Hello</a>
 
add_tag = tag('b')
print(add_tag(content)) # <b>Hello</b>

在这个例子里,我们想要给content加tag功能,但是具体的tag_name是什么样子的要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content)。如果按照面向接口方式实现,我们会先把add_tag写成接口,指定其函数和返回类型,然后分别去实现a和b的add_tag。
但是在闭包的概念中,add_tag就是一个函数,它需要tag_name和content两个参数,只不过tag_name这个参数是打包带走的。所以一开始时就可以告诉我怎么打包,然后带走就行。
上面的例子不太生动,其实我们生活和工作中,闭包的概念也很常见。比如说手机拨号,你只关心电话打给谁,而不会去纠结每个 品牌的手机是怎么实现的,用到了哪些模块。再比如去餐馆吃饭,你只要付钱就可以享受到服务,你并不知道那桌饭菜用了多少地沟油。这些都可以看成闭包,返回来的是一些功能或服务(打电话,用餐),但是这些功能使用了外部变量(天线,地沟油等等)
你也可以把一个类实例看成闭包,当你在构造这个类时,使用了不同的参数,这些参数就是闭包里的包,这个类对外提供的方法就是闭包的功能。但是类远远大于闭包,因为闭包只是 一个可以执行的函数,但是类实例则有可能提供很多方法。

何时使用闭包

其实闭包在python中很常见,只不过你没特别注意这就是一个闭包。比如python中的装饰器Decorator,假如你需要写一个带参数的装饰器,那么一般都会生成闭包。
为什么?因为python的装饰器是一个固定的函数接口形式。它要求你的装饰器函数(或装饰器类)必须接受一个函数再返回一个函数:

# how to define
def wrapper(func1): # 接受一个callable对象
  return func1 # 返回一个对象,一般为函数
 
 
# how to use
def target_func(args): # 目标函数
  pass
 
 
# 调用方式1,直接包裹
result = wrapper(target_func("123"))
 
# 调用方式2,使用@语法,等同于方式1
@wrapper
def target_func(args):
  pass
 
result = target_func()

那么如果你的装饰器带参数呢?那么你就需要在原来的装饰器上再包一层,用于接收这些参数。这些参数(私货)传递到内层装饰器后,闭包就形成了。所以说当你的装饰器需要自定义参数时,一般都会形成闭包(类装饰器除外)

def html_tags(tag_name):
  def wrapper_(func):
    def wrapper(*args, **kwargs):
      content = func(*args, **kwargs)
      return "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
    return wrapper
  return wrapper_
 
 
@html_tags('a')
def hello(name='Toby'):
  return "Hello {}!".format(name)
 
 
# 不用@的写法
# hello = html_tags('b')(hello)
# html_tags('b') 是一个闭包,它接受一个函数,并返回一个函数
 
print(hello()) # <a>Hello Toby!</a>
print(hello("world")) # <a>Hello world!</a>

再了解下闭包到底长什么样子。其实闭包函数相对于普通函数会多出一个__closure__的属性,里面定义了一个元组用于存放所以的cell对象,每个 cell对象一一保存了这个闭包中所有的外部变量。

def make_printer(msg1, msg2):
  def printer():
    print(msg1, msg2)
  return printer
 
 
printer = make_printer('Foo', 'Bar') # 形成闭包
 
print(printer.__closure__) # 返回cell元组
(<cell at 0x000002721C1D2138: str object at 0x000002721C1F2068>, <cell at 0x000002722B199CD8: str object at 0x000002721C1F20A0>)
print(printer.__closure__[0].cell_contents) # 第一个外部变量'Foo'
print(printer.__closure__[1].cell_contents) # 第二个外部变量'Bar'

以上就是详解python中的闭包的详细内容,更多关于python 闭包的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python对两个有序列表进行合并和排序的例子
Jun 13 Python
Python json模块使用实例
Apr 11 Python
通过mod_python配置运行在Apache上的Django框架
Jul 22 Python
Python列出一个文件夹及其子目录的所有文件
Jun 30 Python
python实现给微信公众号发送消息的方法
Jun 30 Python
Python实现进程同步和通信的方法
Jan 02 Python
使用Python处理Excel表格的简单方法
Jun 07 Python
python实现嵌套列表平铺的两种方法
Nov 08 Python
python 猴子补丁(monkey patch)
Jun 26 Python
python对象销毁实例(垃圾回收)
Jan 16 Python
基于jupyter代码无法在pycharm中运行的解决方法
Apr 21 Python
Python调用JavaScript代码的方法
Oct 27 Python
python logging模块的使用
Sep 07 #Python
了解一下python内建模块collections
Sep 07 #Python
Python pymsql模块的使用
Sep 07 #Python
Python虚拟环境的创建和使用详解
Sep 07 #Python
python用Tkinter做自己的中文代码编辑器
Sep 07 #Python
利用Python将图片中扭曲矩形的复原
Sep 07 #Python
python利用递归方法实现求集合的幂集
Sep 07 #Python
You might like
英雄试炼之肉山谷—引领RPG新潮流
2020/04/20 DOTA
PHP PDOStatement::rowCount讲解
2019/02/01 PHP
PHP实现网站应用微信登录功能详解
2019/04/11 PHP
Z-Blog中用到的js代码
2007/03/15 Javascript
Javascript 构造函数 实例分析
2008/11/26 Javascript
Jquery动态改变图片IMG的src地址示例
2013/06/25 Javascript
浅析Javascript中“==”与“===”的区别
2014/12/23 Javascript
jquery实现鼠标经过显示下划线的渐变下拉菜单效果代码
2015/08/24 Javascript
JS实现带圆弧背景渐变效果的导航菜单代码
2015/10/13 Javascript
JS实现鼠标滑过链接改变网页背景颜色的方法
2015/10/20 Javascript
JavaScript希尔排序、快速排序、归并排序算法
2016/05/08 Javascript
vue.js初学入门教程(2)
2016/11/07 Javascript
Javascript使用SWFUpload进行多文件上传
2016/11/16 Javascript
浅谈JS对html标签的属性的干预以及对CSS样式表属性的干预
2017/06/25 Javascript
Angular 2父子组件数据传递之@ViewChild获取子组件详解
2017/07/04 Javascript
微信小程序websocket聊天室的实现示例代码
2019/02/12 Javascript
微信小程序控制台提示warning:Now you can provide attr &quot;wx:key&quot; for a &quot;wx:for&quot; to improve performance解决方法
2019/02/21 Javascript
js实现无刷新监听URL的变化示例代码详解
2020/06/03 Javascript
Python中list循环遍历删除数据的正确方法
2019/09/02 Python
深入浅析Python科学计算库Scipy及安装步骤
2019/10/12 Python
tensorflow之变量初始化(tf.Variable)使用详解
2020/02/06 Python
Python爬取微信小程序通用方法代码实例详解
2020/09/29 Python
Chemist Warehouse官方海外旗舰店:澳洲第一连锁大药房
2017/08/25 全球购物
流行文化收藏品:Sideshow(DC漫画,星球大战,漫威)
2019/03/17 全球购物
安德玛菲律宾官网:Under Armour菲律宾
2020/07/28 全球购物
什么时候用assert
2015/05/08 面试题
石油大学毕业生自荐信
2014/01/28 职场文书
安康杯竞赛活动总结
2014/05/05 职场文书
学雷锋标语
2014/06/25 职场文书
党员剖析材料范文
2014/12/18 职场文书
2015年六年级班主任工作总结
2015/10/15 职场文书
党校团干班培训心得体会
2016/01/06 职场文书
远程教育培训心得体会
2016/01/09 职场文书
经典励志格言:每日一句,让你每天充满能量
2019/08/16 职场文书
Pycharm连接远程服务器并远程调试的全过程
2021/06/24 Python
Flask使用SQLAlchemy实现持久化数据
2021/07/16 Python