python decorator 详解

08 Apr 2016 . . Comments

语法转换

decorator是python里面非常有用的语法。理解decorator的核心在于理解它是如何被解 释器调用的。我们这样使用装饰器的时候

 @myDecorator
def myFunc(a, b, c):
    pass

python将这种语法转换成为 myFunc=myDecorator(myFunc), 然后我们可以像平常一样使 用myFunc(a, b, c)来调用它。从这个转换后的语法我们应该可以看出

  • myDecorator 应该是个可调用的对象,因为这里会使用 myDecorator() 来调用它
  • myDecorator 应该且只能接受一个函数作为参数。
  • 大部分情况下,myDecorator 必须返回一个函数。为什么是大部分时候,后面有说明。
  • 返回的函数应该能够和原来的函数一样接受相同的参数

简单装饰器

基于这些要求,我们可以写一个简单的装饰器

def BlackHoleDecorator(func):
    def blackhole(*args, **kwargs):
        print "I am a blackhole"
        pass
    return blackhole

@BlackHoleDecorator
def do_something():
    print "In do something"
    
do_something()

显然BlackHoleDecorator满足之前所有的要求,因此这段代码可以正常的运行,它的输 出为I am a blackhole。真正执行的函数是装饰器所定义且返回的blackhole

但是正常情况下这应该不是我们所期盼的,decorator的作用是包装一个函数,除了做其 他的工作之外,应该保留原来函数的功能。因此一般我们看到的装饰器形势应该如下:

def VerboseDecorator(func):
    def wrapper(*args, **kwargs):
        print "Before {} is called".format(func.__name__)
        return_value = func(*args, **kwargs)
        print "After {} is called".format(func.__name__)
        return return_value
    return wrapper

@VerboseDecorator
def do_something():
    print "During do_something is called..."

do_something() 
print do_something.__name__ # wrapper

在这里没有任何特别的代码,只是简单的将调用参数传递给了func函数,也就是通过 do_something=VerboseDecorator(do_something)传递的初始函数定义。

但是注意到do_something.__name__的输出,它不是函数名do_something,而是 wrapper。显然通过这种装饰,我们丢失了原来函数的一些属性。比如 docstring,函数 名等等,此时我们需要用到funtools里面的wraps函数。

from functools import wraps
def VerboseDecorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print "Before {} is called".format(func.__name__)
        return_value = func(*args, **kwargs)
        print "After {} is called".format(func.__name__)
        return return_value
    return wrapper

@VerboseDecorator
def do_something():
    print "During do_something is called..."

do_something()
print do_something.__name__ 

此时do_something.__name__输出为正常的函数名。

到这里,你应该掌握了python里面装饰器80%以上的用法。

基于类的装饰器

现在回到myFunc=myDecorator(myFunc)的用法,这里myDecorator一定需要是一个函数吗?不一定,python里面类的初始化也是使用something(args)这种写法。因此python里面也可以基于类来定义装饰器。

class VerboseClassDecorator(object):
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print "Before {} is called".format(self.func.__name__)
        return_value = self.func(*args, **kwargs)
        print "After {} is called".format(self.func.__name__)
        return return_value

@VerboseClassDecorator
def do_anything():
    print "During do_anything is called..."
    
do_anything()
print do_anything.__name__

我们必须为这个class定义__call__函数,想象一下如下一个类定义


class Foo():
    pass
a = Foo()
a()

这段代码会报错,因为一个类对象是不可调用的,必须添加__call__方法,通过__call__,调用初始的函数。

同时do_anything是一个类对象,使用print do_anything.__name__在这里会报错。为了像之前一样保留函数的名字,docstring等属性,必须使用functools.update_wrapper来包装self对象。


import functools
....
    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)

这样,一个基于类的装饰器就写成了。