Luckylau's Blog

你懂python吗(10)

Python的闭包和装饰器

“learning about descriptors creates a deeper understanding of how python works and an appreciation for the elegance of its design”。

​ 在python中,一般存在以下几个变量类型,local:函数内部作用域;enclosing:函数内部与内嵌函数之间;global:全局作用域;build-in:内置作用域。解释器在遇到变量的时候,会按照如下的顺序进行查找:L > E > G > B,简称LEGB。

LEGB的理解?

我们看一下如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
global val
passline = 60
def func(val):
print "0x%x" % id(val) #val对于func函数是local变量,对于in_func是enclosing变量
passline = 90 #我们看到结果是fail,说明passline最终取值是90
if val >= passline:
print "pass"
else:
print "fail"
def in_func():
print 'val',val
return in_func
def Max(val1, val2):
# max is a built-in fun
return max(val1, val2)
if __name__ == '__main__':
fff = func(89)
fff()
print fff.__closure__
print Max(90, 100)
#output
0x10d38a8
fail
val 89
(<cell at 0x7fa84c2750f8: int object at 0x10d38a8>,)
100

​ 在python中,函数是一个对象(可以通过type函数查看),在内存中占用空间;函数执行完成之后内部的变量会被解释器回收,但是如果某变量被返回,则不会回收,因为引用计数器的值不为0;既然函数也是一个对象,他也拥有自己的属性;对于python函数来说,返回的不一定是变量,也可以是函数。我们在func函数中又定义了一个函数in_func,它目的是打印输出变量val,可是在输出过程查找变量的时候发现本地没有,于是它就会去func函数里面找,并且找到了val,val就是我们所说的对于in_func是enclosing变量。

​ 分析代码执行的过程,我们发现func函数返回了in_func函数给了fff,但是没有打印出val,也就是没有返回变量,意味着在调用func完成之后val变量应该已经被解释器回收,但是在执行了 fff() 函数之后却仍然输出了val的值89,为什么呢?其原因就是:如果引用了enclosing作用域变量的话,会将变量添加到函数属性中,当再次查找变量时,不是去代码中查找,而是去函数属性中查找。因为上述结果我们看到fff()函数的__closure__属性拥有一个变量,这个变量的ID和func函数中val变量的ID一样0x10d38a8。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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"
if __name__ == '__main__':
func_100(89)
func_150(89)
#output
pass
fail

使用闭包()实现如下,可见闭包就是内部函数中对enclosing作用域的变量进行引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def set_passline(passline):
print '0x%x' % id(passline)
def cmp(val):
if val >= passline:
print "pass"
else:
print "fail"
return cmp
if __name__ == '__main__':
f_100 = set_passline(60)
print type(f_100)
print f_100.__closure__
f_100(89)
print "****************"
f_150 = set_passline(90)
print type(f_150)
print f_150.__closure__
f_150(89)
#output
0x24a9398
<type 'function'>
(<cell at 0x7fa5730a30f8: int object at 0x24a9398>,)
pass
****************
0x24a9890
<type 'function'>
(<cell at 0x7fa5730a3130: int object at 0x24a9890>,)
fail

什么是装饰器?

“装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”

简而言之:@a 就是将 b 传递给 a(),并返回新的 b = a(b)

装饰器是闭包的一种应用?

​ 闭包:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

​ 上面提到了两个关键的地方: 自由变量 和 函数。望文知意,可以形象的把它理解为一个封闭的包裹,这个包裹就是一个函数,当然还有函数内部对应的逻辑,包裹里面的东西就是自由变量,自由变量可以在随着包裹到处游荡。当然还得有个前提,这个包裹是被创建出来的。

​ 在通过Python的语言介绍一下,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。

我们再来看装饰器发现其本身就是闭包的一种应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def make_bold(fn):
def wrapped():
return '<b>' + fn() + '<b>'
return wrapped
def make_italic(fn):
def wrapped():
return '<i>' + fn() + '<i>'
return wrapped
@make_italic # hello=make_italic(make_bold(hello))
@make_bold # hello=make_bold (hello)
def hello():
return 'hello,python'
if __name__ == '__main__':
print hello()
#output
<i><b>hello,python<b><i>

​ 闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活,现举一例:假设我们仅仅想打印出各类动物的叫声,分别以类和闭包来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal():
def __init__(self,animal):
self.animal=animal
def sound(self,voice):
print self.animal , ':',voice
def Voice(animal):
def sound(voice):
print animal , ':' ,voice
return sound
if __name__ == '__main__':
dog=Animal('dog')
dog.sound('wangwang')
cat=Voice('cat')
cat('miaomiao')
#output
dog : wangwang
cat : miaomiao

​ 可以看到输出结果是完全一样的,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中。

参考:

http://www.jb51.net/article/63331.htm

http://www.cnblogs.com/cotyb/p/5243252.html

https://segmentfault.com/a/1190000004461404

Luckylau wechat
如果对您有价值,看官可以打赏的!