Python 闭包与装饰器

时间:2021-06-24 22:50:10

参考:http://www.cnblogs.com/wilber2013/p/4657155.html

https://segmentfault.com/a/1190000004461404

总结:

1、闭包:字面意思就是包裹一个函数,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是*变量。 2、*变量:当函数A的生命周期结束之后,调用A传递的变量依然存在,因为它被闭包B引用了,所以不会被回收。 3、装饰器:就是对闭包的使用。用来装饰函数。返回一个函数对象,被装饰的函数接收。被装饰函数标识符指向返回的函数对象。(Python中一切皆对象)

解释: 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.装饰器就是对闭包的使用;其实装饰器,就是my_sum = dec(my_sum)的过程。
2.装饰器用来装饰函数;
3.返回一个函数对象,被装饰的函数接收;
4.被装饰函数标识符指向返回的函数对象。

5.语法糖   @deco

def dec(func):
print ('call dec')
def in_dec(*arg):
print ('in dec arg=', arg)
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return func(*arg)
return in_dec

@dec
def my_sum(*arg): #my_sum = in_dec
print ('in my_sum')
return sum(arg)


print my_sum(1,2,3,4,5)

结果:

call dec
('in dec arg=', (1, 2, 3, 4, 5))
in my_sum
15

说明:当python遇到装饰器的时候,就会先调用装饰器的函数,该函数的参数就是被装饰的函数。

第一步:dec(my_sum)->in_dec

第二步:上述返回in_dec,被my_sum接收,则my_sum = in_dec

第三步:当调用my_sum的时候,就先调用dec,然后再调用my_sum