简述对Python装饰器的理解,写一个简单的装饰器。
要理解装饰器,我们先介绍一下几点python的基础知识。
1、作用域(命名空间)及变量生存周期
有过一点编程基础的都知道namespace分为:
local namespace:作用于为当前函数
global namespace:作用域为当前模块
built-in namespace:作用域为所有模块
python的每一个函数都有着自己的命名空间namespace。我们先看两个小程序:
程序1:
a_string='LOVE'
def func1():
print a_string
func1()
print a_string
输出为
LOVE
LOVE
程序2:
a_string='LOVE'
def func1():
a_string='love'
print a_string
func1()
print a_string
输出为:
love
LOVE
在NO.2中我们可以看到a_string被定义了两次:在函数func1()中定义一个局部变量a_string,同时隐藏了全局作用域中的同名变量a_string。这是python特别的地方,在C/C++中是不可行的。我们从NO.1和NO.2程序运行的结果中可以看出,func1()函数首先会在自己的local namespace寻找需要的变量。如果找不到,就会到上一级:global namespace中寻找需要的变量。
也就是说:,Python会按照 local namespace -> global namespace -> build-in namespace的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。
变量的生存周期是随着函数被调用的开始而开始,随着调用的结束而结束。
2、函数的嵌套及函数参数
python中允许嵌套函数,我们可以在函数中定义另一个函数,可以看下例:
def func1():
x=1
def func2():
print x
func2()
func1()
输出:
1
在func1()中定义了另一个函数func2():python解释器需找一个叫x的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层的作用域定义在另外一个函数里面。
在python中,函数和其他东西一样都是对象。你可以将函数看作为一个简单而普通的数值。函数也可以被当作为参数传递给另一个函数。
def func1(x, y):
return x + y
def func2(x, y):
return x - y
def func3(func, x, y):
print func(x,y)
func3(func1, 2, 1)
func3(func2, 2, 1)
输出:
3
1
在上述的代码中,func3()函数中有三个参数,其中第一个就是传入了函数参数。函数的名称和其他的变量一样只是简单的标识符而已。
3、闭包
看起来这是一个很奇怪的概念,抽象来说,它就是指嵌套在非全局作用域中的函数能够记住它在被定义时所在的封闭命名空间。具体我们可以看下例:
def func1():
x=1
def func2():
print x
return func2()
a=func1()
输出:
1
看起来是个非常简单的程序,但是其中的过程需要理解的非常清楚才行。首先在func2()中调用了x变量,在其本地命名空间中未搜索到,之后转向上一级命名空间找到了x的定义。但是我们从变量的生存周期来看,x是随着func1()的调用开始而开始的,在func1()结束时结束。我们无法在func1()结束之后继续调用func2(),因为变量x早已不复存在,理论上运行会发生一个内存错误。但是事实却不是如此,返回的func2()仍然能够继续工作。至此,我们引入python的函数闭包概念,也就是说在func1()中定义的函数能够记住func1()中它所需要的值(如果还有个y则不会被记住,因为func2()不需要)。
上面的三条如果都理解了,那么装饰器也就比较好理解了。装饰器其实就是一个闭包,它将函数作为参数传入,然后返回一个替代版的函数。
def func1(func):
print "1"
func()
print "3"
def func2():
print "2"
func3=func1(func2)
输出:
1
2
3
在这里,func1()就是一个装饰器,我们可以将func3()看作为func2()的一个装饰版本。装饰器改变了函数本来的功能,可以说是一个非常好的工具。
另外python还定义了简单的表达方式,使用@标识符可以将装饰器应用到函数上。只需要在函数的定义之前加上@和装饰器的名称即可。
def func1(func):
print "1"
func()
print "3"
@func1
def func2():
print "2"
两个程序的输出是一样的。这个用法称为语法糖,可以让装饰的行为更加直接。