编写装饰器无外乎涉及到几种参数-->装饰器自己的参数,被装饰函数,被装饰函数的参数,正好对应三个参数入口(也即三个函数)来按顺序接收这些参数;函数式中就是三层嵌套函数,类式装饰器就是初始化方法,__call__方法,加一个__call__下嵌套的包裹方法即可!!
def debug(func):
def wrapper():
print "[DEBUG]: enter {}()".format(func.name)
return func()
return wrapper
def say_hello():
print "hello!"
say_hello = debug(say_hello) # 添加功能并保持原函数名不变
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func):
def wrapper():
print "[DEBUG]: enter {}()".format(func.name)
return func()
return wrapper
@debug
def say_hello():
print "hello!"
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:
def debug(func):
def wrapper(something): # 指定一毛一样的参数
print "[DEBUG]: enter {}()".format(func.name)
return func(something)
return wrapper # 返回包装过函数
@debug
def say(something):
print "hello {}!".format(something)
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。
def debug(func):
def wrapper(args, **kwargs): # 指定宇宙无敌参数
print "[DEBUG]: enter {}()".format(func.name)
print 'Prepare and say...',
return func(args, **kwargs)
return wrapper # 返回
@debug
def say(something):
print "hello {}!".format(something)
至此,你已完全掌握初级的装饰器写法。
高级一点的装饰器
带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见http://betacat.online/posts/python-closure/)
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level):
def wrapper(func):
def inner_wrapper(args, **kwargs):
print "[{level}]: enter function {func}()".format(
level=level,
func=func.name)
return func(args, **kwargs)
return inner_wrapper
return wrapper
@logging(level='INFO')
def say(something):
print "say {}!".format(something)
如果没有使用@语法,等同于
say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do(something):
print "do {}...".format(something)
if name == 'main':
say('hello')
do("my work")
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类实现的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。
class Test():
def call(self):
print 'call me!'
t = Test()
t() # call me
像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def init(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print "[DEBUG]: enter function {func}()".format(
func=self.func.__name__)
return self.func(*args, **kwargs)
@logging
def say(something):
print "say {}!".format(something)
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。
class logging(object):
def init(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print "[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__)
func(*args, **kwargs)
return wrapper #返回函数
@logging(level='INFO')
def say(something):
print "say {}!".format(something)