原文发表在我的博客主页,转载请注明出处

前言

如果把python当作脚本语言,每次就是写个几十行上百行来处理数据的话,装饰器也许不是很必要,但是如果要开发一个大型系统,装饰器是躲不开的,最开始体会ryu的装饰器之美是在阅读ryu源码的时候,用python官网的一句话来说,learning about descriptors creates a deeper understanding of how python works and an appreciation for the elegance of its design。本篇文章从闭包讲起,也从闭包结束,因为如果理解了闭包,也就理解了装饰器。

python函数作用域LEGB

不论在什么语言中,变量都是必不可少的,在python中,一般存在以下几个变量类型,local:函数内部作用域;enclosing:函数内部与内嵌函数之间;global:全局作用域;build-in:内置作用域。解释器在遇到变量的时候,会按照如下的顺序进行查找:L > E > G > B,简称LEGB。下面以一个简单的函数来说明上述过程:

#coding:utf-8
#global var
passline = 60
def func(val):
# for func, it's local var
# for in_func, it's enclosing var
passline = 90
if val >= passline:
print "pass"
else:
print "fail"
def in_func():
print val
return in_func def Max(val1, val2):
# max is a built-in fun
return max(val1, val2) f = func(89)
f()
print Max(90, 100)

local,global,build-in这三个类型比较容易理解,enclosing变量呢?下一节详细讲解。

闭包理解与使用

首先理解下python中的函数,在python中,函数是一个对象(可以通过type函数查看),在内存中占用空间;函数执行完成之后内部的变量会被解释器回收,但是如果某变量被返回,则不会回收,因为引用计数器的值不为0;既然函数也是一个对象,他也拥有自己的属性;对于python函数来说,返回的不一定是变量,也可以是函数。

上一节提到了enclosing变量,在func函数中又定义了一个函数in_func,它输出了变量val,在输出过程中查找变量的时候发现本地没有,所以他就会去func函数里面找并且找到了,这个变量就是enclosing变量。在上面一段代码中,敏感的人可能已经发现了问题,分析代码执行的过程,func函数返回了in_func函数给了f,但是没有返回变量,所以在调用func完成之后val变量应该已经被解释器回收,但是在执行了f函数之后却仍然输出了val的值89,为什么呢?其原因就是:如果引用了enclosing作用域变量的话,会将变量添加到函数属性中,当再次查找变量时,不是去代码中查找,而是去函数属性中查找。可以通过如下代码进行验证:

#coding:utf-8
passline = 60
def func(val):
print "%x" %id(val)
if val >= passline:
print "pass"
else:
print "fail"
def in_func():
print val
in_func()
return in_func f = func(89)
f() #in_func
print f.__closure__

执行上面的代码,可以发现f函数的__closure__属性拥有一个变量,这个变量的ID和func函数中val变量的ID一样。

上面的代码是用来判断学生的成绩是否及格,即在百分制中60分及格,如果现在需要添加新的功能,即150分制中90分作为及格线,如何完成代码呢?最简单的就是我们创建两个函数,分别为func_100和func_150来完成判断,判断逻辑完全一样,代码如下:

#coding:utf-8

def func_150(val):
passline = 90
if val >= passline:
print "pass"
else:
print "fail" def func_100(val):
passline = 60
if val >= passline:
print "pass"
else:
print "fail" func_100(89)
func_150(89)

如果再增加应用场景呢?这样重复而没有任何技术含量的代码的增添十分繁琐,我们可以使用新的方法——闭包来解决这个问题。什么是闭包呢?闭包就是内部函数中对enclosing作用域的变量进行引用。代码如下:

#coding:utf-8
def set_passline(passline):
def cmp(val):
if val >= passline:
print "pass"
else:
print "fail"
return cmp f_100 = set_passline(60)
f_150 = set_passline(90)
print type(f_100)
print f_100.__closure__
f_100(89)
f_150(89)

在上述代码中,我们定义了一个set_passline函数,这个函数返回cmp函数,在这个函数中定义了cmp函数,在cmp函数中是我们之前的逻辑。在之后不论要进行多少分制的及格判断,只需要调用set_passline函数设置及格线就好,我们以百分制60分及格为例,分析上述代码的执行过程,基本分为两步,一是set_passline函数把返回值cmp函数给f_100,同时将60作为属性给cmp函数,二是f_100函数的执行其实相当于执行存储了passline的cmp函数。

接下来考虑一个问题,上面的passline是整数,能否换成函数?既然变量和函数都是对象,而且以python的灵活性,答案是肯定的。下面的代码同时描述了闭包的另一个应用场景,同时展示了如何使用。

#coding:utf-8
def my_sum(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return sum(arg) def my_average(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return sum(arg)/len(arg) def dec(func):
def in_dec(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return func(*arg)
return in_dec # 1.dec return in_dec -> my_sum
# 2.my_sum = in_dec(*arg)
my_sum = dec(my_sum)

在上面的代码中,首先定义了两个函数,这两个函数的功能大同小异,分别为求和函数和求平均值函数,由于求平均值元祖的长度不能为空,同时元祖中的数据都应该为整数,所以在每个函数中都先需要参数检查,本着以人为本的方针,这部门代码应该复用。所以在下面定义了另外一个函数dec,这个函数的参数为一个函数,返回值为一个内嵌函数,这个内嵌函数的逻辑和上面求和求平均值的逻辑一样,先判断,再返回结果。这个函数如何使用呢?比如现在我们要求和,代码如下:

def my_sum(*arg):
return sum(arg) def dec(func):
def in_dec(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return func(*arg)
return in_dec # 1.dec return in_dec -> my_sum
# 2.my_sum = in_dec(*arg)
my_sum = dec(my_sum)

my_sum = dec(my_sum)函数分两步,首先my_sum得到dec返回的in_dec函数,其次调用in_dec函数。

所以,遵上来看,闭包可以在很大程度上实现封装和代码复用。

装饰器

最开始就说,这篇博客始于闭包,终于闭包,所以装饰器不多说,只说四句话:

1.装饰器就是对闭包的使用;

2.装饰器用来装饰函数;

3.返回一个函数对象,被装饰的函数接收;

4.被装饰函数标识符指向返回的函数对象。

总结

接触python装饰器很久了,殊不知从闭包更容易理解装饰器。

python的闭包与装饰器的更多相关文章

  1. Python函数编程——闭包和装饰器

    Python函数编程--闭包和装饰器 一.闭包 关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数).而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量.参数.当其中一个 ...

  2. python之闭包与装饰器

    python闭包与装饰器 闭包 在函数内部定义的函数包含对外部的作用域,而不是全局作用域名字的引用,这样的函数叫做闭包函数. 示例: #-------------------------------- ...

  3. Python之闭包and装饰器

    闭包和装饰器是Python中非常重要的一种语法格式,在日常工作中应用非常广泛. 首先,我先为大家简单的介绍一下闭包的概念. 闭包:闭包是在函数嵌套的基础上,内层函数使用到外层函数的变量,且外层函数返回 ...

  4. 关于python的闭包与装饰器的实验

    首先看闭包,在嵌套函数内添加返回值,可以通过外部函数读取内部函数信息 #encoding=utf-8 #闭包应用 #先定义闭包函数,并使用 def outer(func): def inner(): ...

  5. python中闭包和装饰器

    前言: 编程语言发展的过程中,我们为了提高代码利用率,发明了函数式编程.函数将代码封装起来,我们需要用到此功能函数的时候,调用一下就可以了.但是使用的过程中,也遇到了一些问题,比如函数实现的功能不够, ...

  6. Python的闭包和装饰器

    什么是闭包 python中函数名是一个特殊的变量,它可以作为另一个函数的返回值,而闭包就是一个函数返回另一个函数后,其内部的局部变量还被另一个函数引用. 闭包的作用就是让一个变量能够常驻内存. def ...

  7. Python高级--闭包与装饰器

    前言:在Python中,闭包是一种非常有用的功能!它通常与装饰器一起搭配使用,可以在不改变被装饰函数的功能的基础上,完成更多的功能.如权限认证. 一.如何定义闭包 1.闭包就是两个嵌套的函数,外层函数 ...

  8. python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)

    转载:http://python.jobbole.com/81683/ 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需 ...

  9. Python作用域-->闭包函数-->装饰器

    1.作用域: 在python中,作用域分为两种:全局作用域和局部作用域. 全局作用域是定义在文件级别的变量,函数名.而局部作用域,则是定义函数内部. 关于作用域,我要理解两点:a.在全局不能访问到局部 ...

随机推荐

  1. Spring学习笔记之 Spring IOC容器(二) 之注入参数值,自动组件扫描方式,控制Bean实例化方式,使用注解方式

     本节主要内容:    1. 给MessageBean注入参数值    2. 测试Spring自动组件扫描方式    3. 如何控制ExampleBean实例化方式    4. 使用注解方式重构Jdb ...

  2. URAL 1430 Crime and Punishment

    Crime and Punishment Time Limit:500MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u ...

  3. struts2 基本用法

    Struts2必需库: commons-fileupload.jar.commons-io-1.3.2.jar.freemarker-2.3.16.jar.javassist-3.7.ga.jar.o ...

  4. [书目20160218]微软软件研发的奥秘:MSF精髓

    目录 第一部分 解决方案交付基础 第1章 什么是MSF,它适合你吗 2 1.1 MSF的历史和起源 2 1.2 为什么是“框架” 3 1.3 MSF有何不同 5 1.4 MSF第四版的要素 5 1.5 ...

  5. selenium如何随机选取省份和城市的下拉框的值

    1.原始需求,选择省份后,相应的城市会自动加载 2.思路 a.获取下拉框的所有元素个数 b.随机点击0-元素个数之间的某个值 3.代码实现 Random random = new Random(); ...

  6. MyBatis学习总结(一)

    一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以 ...

  7. 安全框架 - Shiro与springMVC整合的注解以及JSP标签

    Shiro想必大家都知道了,之前的文章我也有提过,是目前使用率要比spring security都要多的一个权限框架,本身spring自己都在用shiro,之前的文章有兴趣可以去扒一下 最近正好用到s ...

  8. [Unity2D]2D Mobile Joystick

    效果预览 操作步骤 1.下载素材 http://pan.bai du.com/s/1gdkQz8v 2.新建一个GUITexture(Joystick)及一个Sprite(Nyan)   3.添加背景 ...

  9. [km] 如何判断一个直播系统是否使用的是RTMP

    如何判断一个直播系统是否使用的是RTMP from: http://peiqiang.net/2016/03/21/how-to-judge-whether-rtmp-is-used-by-a-liv ...

  10. php strcmp引起的问题

    在官方的文档有这么一端说明: Note a difference between 5.2 and 5.3 versions echo (int)strcmp('pending',array()); w ...