装饰器的强大在于它能够在不修改原有业务逻辑的情况下对代码进行扩展,如进行权限校验、用户认证、日志记录、性能测试、事务处理、缓存等,这都是装饰器的绝佳应用场景,它能够最大程度地对代码进行复用,因为装饰器本身也是一个函数。
为了理解装饰器,先看下函数在python中的几种用法。
函数的用法
函数在python中就是一个对象。假如对于如下两个函数
>>>def foo(num):
... return num+1
...
>>>
>>>def bar():
... print("in bar function")
... return foo
...
>>>
看一下它们的用法
给变量赋值
把函数名赋给一个变量,相当于起别名
>>>foo
>>>
>>>bar
>>>
>>>foo=bar
>>>foo
>>>foo()
in barfunction
>>>
函数名字叫foo,可以把 foo 可理解为变量名,该变量指向一个函数对象。经过赋值后,变量名 foo 现在指向bar函数对象。
作为函数返回值
一个函数放在另一个函数中return语句后,如上面所示
调用函数 bar() 的返回值是一个函数对象,因为返回值是函数,所以我们可以继续对返回值进行调用(记住:调用函数就是在函数名后面加())。 变量 foo 指向的对象与 bar() 的返回值是同一个对象。
作为函数参数
对bar和foo函数进行一下修改,如
>>>def foo(num):
... return num+1
...
>>>
>>>def bar(fun):
... print "in bar function"
... print fun
... return fun(3)
...
>>>bar(foo)
in barfunction
4
>>>foo
>>>
函数 bar 接收一个参数,这个参数是一个可被调用的函数对象,把函数 foo 传递到 bar 中去时,foo 和 fun 两个变量名指向的都是同一个函数对象,所以调用 fun(3) 相当于调用 foo(3)。
函数嵌套
函数不仅可以作为参数和返回值,还可以定义在另一个函数中,作为嵌套函数存在。
>>>def outer(outarg):
... outlocalarg=1
... def inner():
... print outarg,outlocalarg
... inner()
...
>>>outer(0)
0 1
>>>
inner做为嵌套函数,它可以访问外部函数的局部变量和形参,调用 outer 函数时,发生了3件事:
1、给变量 outlocalarg 赋值为1
2、定义嵌套函数 inner,此时并不会执行 inner 中的代码,因为该函数还没被调用
3、调用 inner 函数,执行 inner 中的代码逻辑。
闭包函数
def outer(x):
def inner():
print(x)
return inner
closure = outer(1)
closure() # 1
同样是嵌套函数,只是稍改动一下,把局部变量 x 作为参数了传递进来,嵌套函数不再直接在函数里被调用,而是作为返回值返回,这里的 closure就是一个闭包函数,它本质上它还是函数,闭包是引用了自由变量(x)的函数(inner)。
下面具体来看看装饰器
装饰器
装饰器是一个以函数作为参数并返回一个闭包函数的函数。引入装饰器目的在不修改原函数的代码的情况下,添加新的功能。可以在执行原函数之前加,也可以在执行原函数之后添加,而不能在函数的中间添加功能,但只要用装饰器装饰了的函数,那么不管被调用多少次,都是装饰之后的效果。
假如现在要看一个函数执行完需要多长时间,
def foo():
print 'in foo()'
我们可以修改foo函数如,
import time
def foo():
start = time.clock()
print('in foo()')
end = time.clock()
print('used:', end - start)
功能看起来无懈可击
>>> foo()
in foo()
('used:', 0.0)
>>>
但是如果我要看看另外一个函数的执行性能,计算时间的这些与函数本身功能无关的代码我还得写一遍,这很不pythonic,怎么办呢? 想到函数名可以作为参数进行传递,可以再定义一个计时函数,将其他函数的引用传递给他,然后在该计时函数中调用foo并进行计时,这样我们就达到了不改动函数定义的目的。这样的计时函数可能为
def timeit(func):
start = time.clock()
func() #执行foo函数
end =time.clock()
print 'used:', end - start
timeit(foo)
原本我们是调用修改后的foo(),现在变成了timeit(foo),如
>>> timeit(foo)
in foo()
used: 0.0
>>>
代价就是需要额外定义一个timeit函数。我们就来想想办法是否可以通过直接调用foo()就产生调用timeit(foo)的效果,即执行foo函数完后,同时也给出其运行时间。
此时想到将timeit赋值给foo,而且返回的是一个与foo参数列表一致的函数的话,然后,调用foo()的代码就完全不用修改。如下定义
def outer(func):
#定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
def inner():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
#将包装后的函数返回
return inner
根据定义这里的 outer 函数其实就是一个装饰器,看起来像是foo被timeit装饰了。
>>> foo = outer(foo) #传入的参数foo为原函数名
>>> foo()
in foo()
used: 0.0
>>>
Python2.4以后支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。这样可以降低字符输入量,它用在函数的定义处:
@outer
def foo():
print("in foo()")
在定义上加上@这一行与另外写foo = outer(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。
>>> foo()
in foo()
used: 0.0
>>>
由此可见,有了装饰器就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
网友评论