Python的几个高级语法概念浅析(lambda表达式闭包装饰器)


Posted in Python onMay 28, 2016

1. 匿名函数
匿名函数(anonymous function)是指未与任何标识符绑定的函数,多用在functional programming languages领域,典型应用场合:
1) 作为参数传给高阶函数(higher-order function ),如python中的built-in函数filter/map/reduce都是典型的高阶函数
2) 作为高阶函数的返回值(虽然此处的"值"实际上是个函数对象)
与命名函数(named function)相比,若函数只被调用1次或有限次,则匿名函数在语法上更轻量级。
具体语法上,python通过lambda语法支持函数体为表达式的匿名函数,即:python的lambda表达式本质上是个匿名函数,但其函数体只能是个表达式,不能包含其它语句。
此外,高级动态语言常借助匿名函数实现闭包(closure)或装饰器(decorator)等高级语法。
在一些场合下,lambda表达式的使用使得python程序看起来非常简洁。例如,下面是根据value对dict元素做排序的代码示例:

>>> foo = {'father' : 65, 'mother' : 62, 'sister' : 38, 'brother' : 29, 'me' : 28}
>>> sorted(foo.iteritems(), key=lambda x: x[1])
[('me', 28), ('brother', 29), ('sister', 38), ('mother', 62), ('father', 65)]

2. 闭包
闭包(closure)本质上是一个包含了其引用环境(referencing environment)的函数或函数引用,这里的"引用环境"通常由一张表来维护,该表存储了函数体会访问的非局部变量(non-local variables)的引用。
与C语言中的函数指针相比,闭包允许嵌套函数访问其作用域外的non-local变量,这与Python解释器对变量的作用域查找规则有关(Python支持LEGB的查找规则,想深究的话,可以参考<Learning Python>第4版第17章Scopes关于作用域及查找规则的详细讲解,或者查看这篇文章 做快速了解)。
对于运行时内存分配模型会在线性栈上创建局部变量的语言来说(典型如C语言),通常很难支持闭包。因为这些语言底层实现中,若函数返回,则函数中定义的局部变量均会随着函数栈被回收而销毁。但闭包在底层实现上要求其要访问的non-local变量在闭包被执行的时候保持有效,直到这个闭包的生命周期结束,这意外着这些non-local变量只有在其确定不再被使用时才能销毁,而不能随着定义这些变量的函数返回销毁。因此,天生支持闭包的语言通常采用garbage collection的方式管理内存,因为gc机制保证了变量只有不再被引用时才会由系统销毁并回收其内存空间
具体语法上,闭包通常伴随着函数嵌套定义。以Python为例,一个简单的闭包示例如下:

#!/bin/env python
#-*- encoding: utf-8 -*-

def startAt_v1(x):
 def incrementBy(y):
  return x + y 
 print 'id(incrementBy)=%s' % (id(incrementBy))
 return incrementBy

def startAt_v2(x):
 return lambda y: x + y 

if '__main__' == __name__:
 c1 = startAt_v1(2)
 print 'type(c1)=%s, c1(3)=%s' % (type(c1), c1(3))
 print 'id(c1)=%s' % (id(c1))
 
 c2 = startAt_v2(2)
 print 'type(c2)=%s, c2(3)=%s' % (type(c2), c2(3))

执行结果如下:

id(incrementBy)=139730510519782
type(c1)=<type 'function'>, c1(3)=5
id(c1)=139730510519782
type(c2)=<type 'function'>, c2(3)=5

上述示例中,startAt_v1和startAt_v2均实现了闭包,其中:v1借助嵌套定义函数实现;v2则借助lambda表达式/匿名函数来实现。
我们以v1为例对闭包做说明:
1) 函数startAt_v1接受1个参数,返回1个函数对象,而这个函数对象的行为由嵌套定义的函数incrementBy实现。
2) 对函数incrementBy来说,变量x就是所谓的non-local变量(因为x既非该函数定义的局部变量,又非普通意义上的全局变量),incrementBy实现具体的函数行为并返回。
3) main入口的c1接收到的返回值是个函数对象,从id(incrementBy) == id(c1)可断定,c1"指向"的对象与函数名incrementBy"指向"的其实是同一个函数对象。
4) 受益于Python对闭包的支持,与普通函数的对象相比,c1指向的对象可以访问不在其函数作用域内的non-local变量,而这个变量是由incrementBy的外层包装函数startAt_v1的入参提供的,于是,相当于c1指向的函数对象对其外层包装函数的入参具有"记忆"功能,通过调用外层包装函数创建闭包时,不同的入参被内层函数作为引用环境维护起来。
5) 调用c1(3)时,传入的参数与引用环境维护的外层包装函数的参数一起运算得到最终结果。
以上步骤分析说明了一个闭包从创建到执行的基本原理,理解这个case后,闭包的概念也应该清晰了。

3. 装饰器
python支持装饰器(decorator)语法。装饰器的概念对于初学者来说比较晦涩,因为它涉及到函数式编程的几个概念(如匿名函数、闭包),这也是本文先介绍匿名函数和闭包的原因。

我们引用这篇文章对装饰器的定义:
A decorator is a function that takes a function object as an argument, and returns a function object as a return value.
从这个定义可知,装饰器本质上只是一个函数,它借助闭包的语法去修改一个函数(又称被装饰函数)的行为,即decorator其实是个闭包函数,该函数以被装饰函数名(这个函数名其实是一个函数对象的引用)作为入参,在闭包内修改被装饰函数的行为后,返回一个新的函数对象。
特别说明:decorator并非必须以函数形式出现,它可以是任何可被调用的对象,例如它也可以class形式出现,参见这篇文章给出的例子。
在定义好函数装饰器的前提下,当外部调用这个被装饰函数时,decorator的语法糖会由Python解释器解释为先执行装饰器函数,然后在装饰器返回的新函数对象上继续执行其余语句。
来个实例分析一下:

#!/bin/env python
#-*- encoding: utf-8 -*-

def wrapper(fn):
 def inner(n, m):
  n += 1
  print 'in inner: fn=%s, n=%s, m=%s' % (fn.__name__, n, m)
  return fn(n, m) + 6 // 这里有return且返回值为int对象
 return inner

@wrapper
def foo(n, m):
 print 'in foo: n=%s, m=%s' % (n, m)
 return n * m

print foo(2, 3)

上面的示例中,foo通过@wrapper语法糖声明它的装饰器是wrapper,在wrapper中,定义了嵌套的inner函数(该函数的参数列表必须与被装饰函数foo的参数列表保持一致),装饰器wrapper修改foo的行为后,返回inner(注意:由于inner的返回值是个int对象,故wrpper最终返回的也是个int对象)。
调用foo(2, 3)时,Python解释器先调用wrapper对foo做行为改写,然后返回int对象,不难推测,上述代码的执行结果如下:

in inner: fn=foo, n=3, m=3
in foo: n=3, m=3
foo(2, 3)=15
Python 相关文章推荐
python 判断一个进程是否存在
Apr 09 Python
Python的Flask框架中实现简单的登录功能的教程
Apr 20 Python
举例讲解Python的lambda语句声明匿名函数的用法
Jul 01 Python
解决python使用open打开文件中文乱码的问题
Dec 29 Python
Python基于socket实现简单的即时通讯功能示例
Jan 16 Python
python实现pdf转换成word/txt纯文本文件
Jun 07 Python
python 读取视频,处理后,实时计算帧数fps的方法
Jul 10 Python
python: 判断tuple、list、dict是否为空的方法
Oct 22 Python
Pandas之ReIndex重新索引的实现
Jun 25 Python
基于python实现把json数据转换成Excel表格
May 07 Python
PyCharm最新激活码PyCharm2020.2.3有效
Nov 18 Python
python和opencv构建运动检测器的实现
Mar 03 Python
python自动翻译实现方法
May 28 #Python
详解Python编程中对Monkey Patch猴子补丁开发方式的运用
May 27 #Python
Python程序中的观察者模式结构编写示例
May 27 #Python
Windows下python2.7.8安装图文教程
May 26 #Python
Java Web开发过程中登陆模块的验证码的实现方式总结
May 25 #Python
剖析Python的Twisted框架的核心特性
May 25 #Python
实例解析Python的Twisted框架中Deferred对象的用法
May 25 #Python
You might like
PHP错误抑制符(@)导致引用传参失败Bug的分析
2011/05/02 PHP
PHP中PDO的错误处理
2011/09/04 PHP
php命名空间学习详解
2014/02/27 PHP
PHP实现上传图片到数据库并显示输出的方法
2018/05/31 PHP
PHP的mysqli_sqlstate()函数讲解
2019/01/23 PHP
Ext第一周 史上最强学习笔记---GridPanel(基础篇)
2008/12/29 Javascript
JS 面向对象的5钟写法
2009/07/31 Javascript
jquery formValidator插件ajax验证 内容不做任何修改再离开提示错误的bug解决方法
2013/01/04 Javascript
Javascript Web Slider 焦点图示例源码
2013/10/10 Javascript
js实现仿百度汽车频道选择汽车图片展示实例
2015/05/06 Javascript
js将滚动条滚动到指定位置的简单实现方法
2016/06/25 Javascript
JavaScript中访问id对象 属性的方式访问属性(实例代码)
2016/10/28 Javascript
jQuery中的select操作详解
2016/11/29 Javascript
浅谈Nodejs中的作用域问题
2016/12/26 NodeJs
JS字符串长度判断,超出进行自动截取的实例(支持中文)
2017/03/06 Javascript
js自定义弹框插件的封装
2020/08/24 Javascript
vue仿淘宝订单状态的tab切换效果
2020/06/23 Javascript
vue.js过滤器+ajax实现事件监听及后台php数据交互实例
2018/05/22 Javascript
JS将网址url转化为JSON格式的方法
2018/07/02 Javascript
JS对象和字符串之间互换操作实例分析
2019/02/02 Javascript
泛谈JS逻辑判断选择器 || &amp;&amp;
2019/05/24 Javascript
Ant design vue table 单击行选中 勾选checkbox教程
2020/10/24 Javascript
vue封装自定义指令之动态显示title操作(溢出显示,不溢出不显示)
2020/11/12 Javascript
python的mysqldb安装步骤详解
2017/08/14 Python
python 简单备份文件脚本v1.0的实例
2017/11/06 Python
python自动查询12306余票并发送邮箱提醒脚本
2018/05/21 Python
Python 在 VSCode 中使用 IPython Kernel 的方法详解
2020/09/05 Python
python 检测nginx服务邮件报警的脚本
2020/12/31 Python
英国蜡烛、蜡烛配件和家居香氛购买网站:Yankee Candle
2018/12/12 全球购物
可持续未来的时尚基础:Alternative Apparel
2019/05/06 全球购物
SQL注入攻击的种类有哪些
2013/12/30 面试题
投资合作协议书
2014/04/17 职场文书
事业单位鉴定材料
2014/05/25 职场文书
电话客服专员岗位职责
2014/06/28 职场文书
余世维讲座观后感
2015/06/11 职场文书
MYSQL 无法识别中文的永久解决方法
2021/06/03 MySQL