深入理解python中的闭包和装饰器


Posted in Python onJune 12, 2016

python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

以下说明主要针对 python2.7,其他版本可能存在差异。

也许直接看定义并不太能明白,下面我们先来看一下什么叫做内部函数:

def wai_hanshu(canshu_1):

  def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数
    return canshu_1*canshu_2

  return nei_hanshu  # 我将内部函数返回出去

a = wai_hanshu(123)   # 此时 canshu_1 = 123
print a
print a(321)  # canshu_2 = 321

深入理解python中的闭包和装饰器

我在函数里面有嵌套了一个函数,当我向外层函数传递一变量的之后,并赋值给 a ,我们发现 a 变成了一个函数对象,而我再次为这个函数对象传参的时候,又获得了内部函数的返回值。我们知道,按照作用域的原则来说,我们在全局作用域是不能访问局部作用域的。但是,这里通过讨巧的方法访问到了内部函数。。

下面我们继续看一个例子:

def wai_hanshu():
  a = []
  def nei_hanshu(canshu):
    a.append(canshu)
    return a

  return nei_hanshu

a = wai_hanshu()
print a(123)
print a(321)

深入理解python中的闭包和装饰器

可以看出函数位于外部函数中的列表 a 竟然改变了。要知道为什么,就要先知道什么是python的命名空间,而命名空间就是作用域表现的原因,这里我简要说明一下。

引入命名空间的主要原因还是为了避免变量冲突,因为python中的模块众多,模块中又有函数,类等,它们都要使用到变量。但如果每次都要注意不和其他变量名冲突,那就太麻烦了,开发人员应该专注于自己的问题,而不是考虑别人写的程序中用到了什么变量,所以python引入了命名空间。命名空间分为模块层,模块内又分为全局作用域和局部作用域,用一个图来表示的话:

深入理解python中的闭包和装饰器

模块之间命名空间不同,而里面还有全局作用域和局部作用域,局部作用域之前还能嵌套,这样就能保证变量名不冲突了。这里顺便补充一下,可以通过 __name__ 属性获取命名空间的名字:

深入理解python中的闭包和装饰器

主文件的命名空间是叫做 '__main__',而模块的命名空间就是模块名。

作用域的诞生,是因为当python在寻找一个变量的时候,首先会在当前的命名空间中寻找,如果当前命名空间中没有,就到上一级的命名空间中找,以此类推,如果最后都没找到,则触发变量没找到的异常。

我们之前一直说:全局作用域无法访问局部作用域,而局部作用域能够访问全局作用域就这这个原因。而当我在局部作用域创建了一个和外面同名的变量时,python在找这个变量的时候首先会在当前作用域中找,找到了,就不继续往上一级找了。

在早期的python版本时,局部作用域是不能访问其他的局部作用域的,只能访问全局的,而现在的版本都是依次向上一级找,这里就提一下。

也就是因为这个特性,我们可以在内部函数中访问外部函数中的变量,这也就是所谓的闭包了。

注意:这里要做好对象之间的区分,例如:

def wai_hanshu():
  a = []
  def nei_hanshu(canshu):
    a.append(canshu)
    return a

  return nei_hanshu

a = wai_hanshu()  # 我创建了一个对象
b = wai_hanshu()  # 我又创建了一个对象
print a
print b
print a(123)
print b(321)

深入理解python中的闭包和装饰器

在这里,我们虽然都是操作 wai_hanshu 中的变量,但是 a 和 b 完全是两个对象,它们所在的内存空间也是不同的,所以里面的数据也是独立的。要注意不要搞混。

装饰器

其实装饰器就是在闭包的基础上多进行了几步,看代码:

def zsq(func): # 装饰函数
  def nei():
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei

def login():  # 被装饰函数
  print '我进行了登录功能'

login = zsq(login)  # 我将被装饰的函数传入装饰函数中,并覆盖了原函数的入口

login()   # 此时执行的就是被装饰后的函数了

深入理解python中的闭包和装饰器

在看这段代码的时候,要知道几件事:

1.函数的参数传递的其实是引用,而不是值。

2.函数名也是一个变量,所以可以重新赋值。

3.赋值操作的时候,先执行等号右边的。

只有明白了上面这些事之后,再结合一下代码,应该就能明白什么是装饰器了。所谓装饰器就是在闭包的基础上传递了一个函数,然后覆盖原来函数的执行入口,以后调用这个函数的时候,就可以额外实现一些功能了。装饰器的存在主要是为了不修改原函数的代码,也不修改其他调用这个函数的代码,就能实现功能的拓展。

而python觉得让你每次都进行重命名操作实在太不方便,于是就给出了一个便利的写法:

def zsq(func):
  def nei():
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei

@zsq  # 自动将其下面的函数作为参数传到装饰函数中去
def login():
  print '我进行了登录功能'


login()

深入理解python中的闭包和装饰器

这些小便利也叫做python的语法糖,你可能在很多地方见过这个说法。

带参数的装饰器:

def zsq(a):
  print '我是装饰器的参数', a
  def nei(func):
    print '我在传入的函数执行之前做一些操作'
    func() # 执行函数
    print '我在目标函数执行后再做一些事情'
  return nei


@zsq('123')
def login():
  print '我进行了登录功能'

深入理解python中的闭包和装饰器

相当于: login = zsq(123)(login) ,所以在这里没有调用就执行了。

装饰器的嵌套:

这里就不完整写个例子了:

@deco1(deco_arg) 
@deco2 
def func(): 
  pass

相当于: func = deco1(deco_arg)(deco2(func)) 

也就是从上到下的嵌套了。

关于闭包和装饰器就先讲到这里,以后有需要再补充。

以上这篇深入理解python中的闭包和装饰器就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用python的chardet库获得文件编码并修改编码
Jan 22 Python
在服务器端实现无间断部署Python应用的教程
Apr 16 Python
深入理解 Python 中的多线程 新手必看
Nov 20 Python
对pandas中apply函数的用法详解
Apr 10 Python
pandas的object对象转时间对象的方法
Apr 11 Python
Python一句代码实现找出所有水仙花数的方法
Nov 13 Python
Falsk 与 Django 过滤器的使用与区别详解
Jun 04 Python
Python3读写Excel文件(使用xlrd,xlsxwriter,openpyxl3种方式读写实例与优劣)
Feb 13 Python
Python matplotlib实时画图案例
Apr 23 Python
CentOS 7如何实现定时执行python脚本
Jun 24 Python
python 调整图片亮度的示例
Dec 03 Python
Pytorch中使用ImageFolder读取数据集时忽略特定文件
Mar 23 Python
Python编码爬坑指南(必看)
Jun 10 #Python
浅析Python中的for 循环
Jun 09 #Python
Python多层嵌套list的递归处理方法(推荐)
Jun 08 #Python
Python-嵌套列表list的全面解析
Jun 08 #Python
PYTHON压平嵌套列表的简单实现
Jun 08 #Python
Python用Bottle轻量级框架进行Web开发
Jun 08 #Python
浅谈Python数据类型之间的转换
Jun 08 #Python
You might like
2021年最新CPU天梯图
2021/03/04 数码科技
PHP函数篇详解十进制、二进制、八进制和十六进制转换函数说明
2011/12/05 PHP
实例说明js脚本语言和php脚本语言的区别
2019/04/04 PHP
javascript Select标记中options操作方法集合
2008/10/22 Javascript
深入理解jquery跨域请求方法
2016/05/18 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
2016/09/17 Javascript
JavaScript遍历Json串浏览器输出的结果不统一问题
2016/11/03 Javascript
正则 js分转元带千分符号详解
2017/03/08 Javascript
angularjs $http实现form表单提交示例
2017/06/09 Javascript
Javascript 严格模式use strict详解
2017/09/16 Javascript
用Webpack构建Vue项目的实践
2017/11/07 Javascript
webpack多页面开发实践
2017/12/18 Javascript
JavaScript设计模式之工厂模式简单实例教程
2018/07/03 Javascript
vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新
2019/05/14 Javascript
微信小程序云开发之模拟后台增删改查
2019/05/16 Javascript
微信小程序 select 下拉框组件功能
2019/09/09 Javascript
p5.js实现故宫橘猫赏秋图动画
2019/10/23 Javascript
Vue.js页面中有多个input搜索框如何实现防抖操作
2019/11/04 Javascript
微信小程序indexOf的替换方法(推荐)
2020/01/14 Javascript
在vue项目实现一个ctrl+f的搜索功能
2020/02/28 Javascript
vue-cli4项目开启eslint保存时自动格式问题
2020/07/13 Javascript
[00:17]DOTA2荣耀之路5:It’s a disastah!
2018/05/28 DOTA
[01:00:25]NB vs Secret 2018国际邀请赛小组赛BO1 B组加赛 8.19
2018/08/21 DOTA
[00:10]神之谴戒
2019/03/06 DOTA
Python中的复制操作及copy模块中的浅拷贝与深拷贝方法
2016/07/02 Python
在Python程序员面试中被问的最多的10道题
2017/12/05 Python
Python即时网络爬虫项目启动说明详解
2018/02/23 Python
总结python中pass的作用
2019/02/27 Python
详解Python打包分发工具setuptools
2019/08/05 Python
python将四元数变换为旋转矩阵的实例
2019/12/04 Python
Windows 平台做 Python 开发的最佳组合(推荐)
2020/07/27 Python
沙特阿拉伯网上购物:Sayidaty Mall
2018/05/06 全球购物
公司法人授权委托书范本
2014/09/12 职场文书
个人四风问题对照检查材料
2014/10/01 职场文书
如何书写授权委托书?
2019/06/25 职场文书
Vue Element plus使用方法梳理
2022/12/24 Vue.js