1,闭包(closure)
闭包是Python所支持的一种特性,它让在非global scope定义的函数可以引用其外围空间中的变量,这些外围空间中被引用的变量叫做这个函数的环境变量。环境变量和这个非全局函数一起构成了闭包。
1 def outer(x): 2 y = [1,2,3] 3 def inner(): 4 print x 5 print y 6 return inner 7 8 x = 5 #这个x没有被引用 9 f = outer(2) 10 f() 11 print f.__closure__ #函数属性__closure__存储了函数的环境变量
x和y都是属于函数outer命名空间的,在inner中被引用,当outer函数退出后,outer的命名空间不存在了,但是inner依然维护了其定义时候对其外部变量x,y的连接。
程序输出:
2
[1, 2, 3]
(<cell at 0x7f672a5b98d8: int object at 0x23a3c50>, <cell at 0x7f672a5b9910: list object at 0x7f672a5a03f8>)
2,装饰器(Decorator)
装饰器是一个可调用对象(a callable),在Python中,函数是对象,当然也是可调用的,所以装饰器可以是一个函数,我们称其为函数装饰器。
这个可调用对象以一个函数作为参数,闭且返回另一个函数(来替换参数那个函数)。
比如:
1 def entrance(func): 2 def inner(): 3 print "inside function :", func.__name__ 4 func() 5 return inner
entrance是一个装饰器,它是一个函数,它可以接收一个函数func作为参数,返回了另一个函数inner。
那为什么叫装饰器了,在返回函数inner()的内部,调用了func(),而且还作了额外的操作,相当于“装饰”了函数func。
那如何使用装饰器?
1 def fun1(): 2 pass 3 fun1 = entrance(fun1) 4 5 def fun2(): 6 pass 7 fun2 = entrance(fun2)
fun1,fun2的名字都没有变,但是通过调用函数装饰器entrance(),它们已经指向了另一个函数inner(),“装饰了”自己。
@操作符
Python提供的@符号,实质上就是上面做的,对一个函数名进行从新赋值,是语法上的技巧。所以上面的代码等价于
1 @entrance 2 def fun1(): 3 pass 4 5 @entrance 6 def fun2(): 7 pass
装饰器的用途?
从这个刻意构造的很简单的例子,可以看出装饰器的意义,如果一个函数需要一个功能,如果这个功能可以被使用在很多函数上,或是函数并不是自己实现,那可以写个装饰器来实现这些功能。
上面的装饰器entrance,装饰一个函数后,函数被调用时会打印出这个函数的名字。
但是有一个问题,这个装饰器从功能上看,是要应该可以用来装饰任何函数,但是如果我们用它来装饰了一个带参数的函数
1 @entrance 2 def fun3(x): 3 pass
只要不调用fun3,这三行代码是不会让Python解释器报错的,因为我们已经知道,它等价于:
def fun3(x): pass fun3 = entrance(fun3)
我们定义了一个带参的函数fun3,然后把fun3指向了另一个函数inner(),当然不会有什么错。
但是,当我们使用fun3时,我们肯定会按照它定义时的样子去使用它,给它传入一个参数。
>>>fun3(1)
这里就会出错了,看看解释器怎么报错的
Traceback (most recent call last):
File "decorator.py", line 23, in <module>
fun3(1)
TypeError: inner() takes no arguments (1 given)
当然我们已经很容易知到为什么会这样报错了,fun3已经不是指向它定义时那个函数了,它现在指向了"inner()",而inner是没有参数的,当然会出错。
那怎么解决呢?
修改一下inner()的定义,让它可以就收任意个参数就可以了 【注:参见Python函数一文】
1 def entrance(func): 2 def inner(*args, **kvargs): 3 print "inside function : ", func.__name__ 4 func(*args, **kvargs) 5 return inner
现在,给inner传任意个参数都不会出错了,也就是entrance可以被用来装饰任何一个函数了。
3,写个装饰器logger
一个函数被调用时,在日志里记录其名称和被调用的实际参数
1 def logger(func): 2 def inner(*args, **kvargs): 3 print func.__name__, 'called, arguments: ', args, kvargs 4 func(*args, **kvargs) 5 return inner 6
所谓闭包其实是很容易理解的,还记得python的作用域规则以及名字的搜索规则吗?
最内嵌套作用域规则- 由一个赋值语句引入的名字在这个赋值语句所在的作用域里是可见的,而且在其内部嵌套的每个作用域也是可以见的,除非它被嵌套于内部的,引进同样名字的另一条赋值语句所覆盖。
LEGB:为了找到某个给定名字所引用的对象,首先应该在这个名字的当前作用域查找,如果找到对应的约束(即引用有效),它就是与这个名字相关的活动约束。否则,就应该到直接的外围作用域(名字空间)查找,并继续向外顺序的检查外围作用域,直到达到最外嵌套层次(这个最外嵌套层也就是module自身所定义的那个作用域)。
我们来看看这个实例:
代码【1】处a为2。根据legb规则,此时名称a对应的有效约束即为函数g的外层嵌套域函数f的名字空间,此时与a对应的有效约束为2.
代码【2】处实际上就是闭包的表现。这里func名字对应的有效约束就是函数g的对象。初看上去会让人产生疑问,因为函数f内的约束‘a=2’在其之外应该是不起作用的,当执行func()时,起作用的约束应该是‘a=1’才对。但是python在执行‘func=f()’时,会执行'def g()'语句,这时python会将约束'a=2'与函数g对应的函数对象捆绑在一起(python内部的实现其实是把a=2 这个约束,传给函数g运行时建立栈帧fram对应的代码块的code.co_freevars中。 code.co_freevars:tuple of string),然后将捆绑结果返回。这个捆绑的整体被称为‘闭包’。实际上‘闭包’就是legb规则的一种实现方案。