装饰器是什么
装饰器本质上是一个Python函数,它可以让其他函数在不改变代码的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
高阶函数
python的装饰器本质上是一个高阶函数,让我们先来看一下高阶函数是怎么工作的。
python中一切皆对象,函数也不例外,因此可以把函数作为参数传入另外一个函数,这种使用方式就构成了高阶函数。
举个例子。
def foo(x, y, f):
return f(x) + f(y)
函数foo的参数f,也是一个函数对象,在函数foo中调用了函数f。
用高阶函数扩展原函数的功能
用一个示例来说明。
# 不使用语法糖,为函数增加额外的功能
def deco(func):
print("before func() called.")
func()
print("after func() called.")
def myfunc():
print(" myfunc() called.")
deco(myfunc)
$ python test.py
before func() called.
myfunc() called.
after func() called.
改变原函数名指向的函数对象
通过使用高阶函数,我们为原来的函数增加了额外的功能。上面的例子中每次都要调用两个函数,有没有更简捷的方式呢?
我们来看一个演化的例子。
# 不使用语法糖,为函数增加额外的功能
def deco(func):
def wrap():
print("before func() called.")
func()
print("after func() called.")
return wrap
def myfunc():
print("myfunc() called.")
myfunc = deco(myfunc)
myfunc()
$ python test.py
before func() called.
myfunc() called.
after func() called.
上面的代码实现了相同的功能,把标识名myfunc修改为deco(myfunc)后,以后直接用原函数名使用就可以了。但是,标识符myfunc指向的函数对象其实已经发生了变化,我们可以把函数对象的id打印出来,看看这种变化。
# 不使用语法糖,为函数增加额外的功能
def deco(func):
def wrap():
print("before func() called.")
func()
print("after func() called.")
print 'wrap', id(wrap)
return wrap
def myfunc():
print("myfunc() called.")
print 'before deco', id(myfunc)
myfunc = deco(myfunc)
print 'after deco', id(myfunc)
#myfunc()
$ python test.py
before deco 139931853084264
wrap 139931853084384
after deco 139931853084384
可以看到,执行deco之后,原函数已经变为wrap函数对象。以后每次调用myfunc函数,其实是在调用wrap函数,并且wrap函数内可以调用原myfunc指向的函数。
注意,wrap函数对象是在执行myfunc = deco(myfunc)时生成的,如果有其他代码执行foo = deco(foo),则生成另外一个wrap函数对象。
使用语法糖@
def deco(func):
def wrap():
print("before func() called.")
func()
print("after func() called.")
return wrap
@deco
def myfunc():
print("myfunc() called.")
myfunc()
myfunc()
# print myfunc.__name__
# print myfunc.func_name
$ python test.py
before func() called.
myfunc() called.
after func() called.
before func() called.
myfunc() called.
after func() called.
使用语法糖@装饰函数,实质上就是执行了一次myfunc = deco(myfunc)
被包装函数带参数
def deco(func):
def wrap(*args, **kwargs):
print("before func() called.")
func(*args, **kwargs)
print("after func() called.")
return wrap
@deco
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
myfunc(2, 5)
$ python test.py
before func() called.
a + b = 7
myfunc() called.
after func() called.
如果被装饰函数有多个参数,应该怎么办呢?可以用位置参数和关键字参数的形式把原函数参数传到包装函数里。
myfunc被包装后,实际上指向wrap函数对象。因此,myfunc的参数应被传递到wrap,wrap应该具有相应的参数列表。但由于装饰器可以修改多个函数,因此wrap的参数事先是不确定的。我们可以用位置参数和关键字参数的形式表示一切可能的参数,在wrap中执行原函数时,再把位置参数和关键字参数解析出来,就把参数完美地传递给原函数了。
装饰器带参数
TO DO
被装饰函数的属性
使用装饰器后,原函数的标识符已指向包装函数,其函数属性在外观上发生了变化,看下面的例子。
def deco(func):
def wrap(*args, **kwargs):
print("before func() called.")
func(*args, **kwargs)
print("after func() called.")
return wrap
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name
$ python test.py
myfunc name: myfunc
myfunc name: wrap
可以看到,由于myfunc被装饰后实际指向wrap函数,因此函数名为wrap。如果我们想保留函数名为myfunc,应该怎么做呢?
有几种方法可以做到,一种是在装饰器直接修改wrap的函数名,另一种是使用functools自动为我们完成,还可以利用wrapt第三方包。
# 方法一
def deco(func):
def wrap(*args, **kwargs):
print("before func() called.")
func(*args, **kwargs)
print("after func() called.")
wrap.func_name = func.func_name
return wrap
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name
# 方法二
import functools
def deco(func):
@functools.wraps(func)
def wrap(*args, **kwargs):
print("before func() called.")
func(*args, **kwargs)
print("after func() called.")
return wrap
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
print 'myfunc name:', myfunc.func_name
myfunc = deco(myfunc)
print 'myfunc name:', myfunc.func_name
$ python test.py
myfunc name: myfunc
myfunc name: myfunc
使用wrapt简化装饰器的实现
嵌套的装饰器函数很不直观,我们可以用第三方包简化装饰器的实现,使代码可读性更好。
wrapt是一个功能非常完善的包,可用于实现各种你想到或没想到的装饰器。使用wrapt实现的装饰器解决之前讨论的函数属性问题,甚至inspect.getsource(func)也准确无误。
我们通过示例来学习一下用wrapt实现装饰器。
# 不带参数的装饰器
import wrapt
@wrapt.decorator
def logging(wrapped, instance, args, kwargs):
print '[DEBUG]: enter {}()'.format(wrapped.__name__)
return wrapped(*args, **kwargs)
@logging
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
myfunc(3)
# 带参数的装饰器
import wrapt
def logging(level):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
print '[DEBUG]: enter {}()'.format(wrapped.__name__)
return wrapped(*args, **kwargs)
return wrapper
@logging(level='INFO')
def myfunc(a, b=7):
print 'a + b = %s' % (a + b)
print("myfunc() called.")
myfunc(3)