在 python 中,我们经常可以看到这样的函数定义:

@staticmethod
def func():
    ... ...

不去看第一句,剩下的就是一个普通的函数定义了,那么加上第一句 @staticmethod 是什么意思?又有什么用处呢?

我们从字面上可以猜测,这里定义的 func 函数是 静态函数,至于是什么是静态函数,在本文中就不做介绍了 (如果想了解的话可以阅读我之前写过的这篇文章 Python StaticMethod and Classmethod 了解)。那加上这个之后有什么用,python 对我们定义的 func 函数做了什么操作呢?

如果你看过一些关于 staticmethod 的资料的话,你也许会知道最开始定义 staticmethod 的方式是:

func = staticmethod(func)

也就是说,加入我要在类 Foo 中定义一个静态函数,那么我需要这么写:

class Foo(object):
    def func():
        ... ...
    func = staticmethod(func)

Foo.func()

看一下这种方式,我们发现明显很累赘,所以装饰器就来解救我们于繁琐之中,我们只需要加一个装饰器,就将函数定义简化成了:

class Foo(object):
    @staticmethod
        def func():
            ... ...

    Foo.func()

所以,从这里我们就知道了装饰器就是简化函数调用的一种方式

@wrap
def func():
    ... ...
func()

等价于:

wrap(func)()

举个栗子

说了一些概念之后,我们是时候上个例子了,这个例子我想实现的功能就是能够计算函数执行的时间,例如xx 函数执行花费了 3S 之类的,代码如下:

    def my_decorator(func):
        def wrapper_fun():
            begin_time = time()
            func()
            end_time = time()
            print int(end_time - begin_time)
        return wrapper_fun

    @my_decorator
    def test():
        print "test func run"
        sleep(3)

    test()

这个例子很简单,就是计算一下 test 函数执行了多久,我们解析开来看其实调用就是这样的:my_decorator(test)() 然后就会打印出花费的时间。

带参数的函数

第一个例子太简单了,以至于我们都没感觉到有趣,所以这次我们加上个参数试试:

    def my_decorator(func):
        def wrapper_fun():
            begin_time = time()
            func()
            end_time = time()
            print int(end_time - begin_time)
        return wrapper_fun

    @my_decorator
    def test(arg):
        print "test func run with arg: {}".format(arg)

    test('hello')

然后跑一遍,发现果断出错了。我们分析一下问题,这里的调用解开来其实就是:my_decorator(test)('hello')

my_decorator(test) 返回的是 wrapper_fun(),没有带参数,所以代码执行失败很正常。既然知道了原因,那么修改起来也方便了,就是给 wrapper_fun 加上参数:

    def my_decorator(func):
        def wrapper_fun(*args, **kwargs):
            print "decorator begin"
            func(*args, **kwargs)
            print "decorator end"
        return wrapper_fun

    @my_decorator
    def test(arg):
        print "test func run with arg: {}".format(arg)

    test('hello')

然后跑一遍代码,运行起来了,并且打印出:

    decorator begin
    test func run with arg: hello
    decorator end

装饰器带参数

终于有点意思了,现在我们可以调用带参数的函数了,但是,喜欢瞎折腾的我又有了新的想法,我好想见过装饰器带参数的,那这样怎么实现呢?根据套路,我们还是以解开的函数调用来进行思考:

如果装饰器带参数,那么,我们是不是可以这样子:my_decorator(test, 'decorator_arg')('hello') 这样做的话,那么我们的装饰器要怎么写呢?这样子?:

    def my_decorator(func, *oargs, **okwargs):
            def wrapper_fun(*iargs, **ikwargs):
                print "decorator begin"
                func(*iargs, **ikwargs)
                print "decorator end"
            return wrapper_fun

那明显不行啊,因为装饰器是 Python 自带的语法糖,我们不能控制它的参数个数,就只能是一个参数,而且就是我们被封装的函数,不然就不是装饰器了。那说明我们这个方法不行,那就换个思路:

    my_decorator('decorator_arg')(test)('hello')

这样子行不行,这样子的话,我们既给装饰器带了参数,又给函数带了参数,那么转换成装饰器能不能实现呢?试一下:

    def my_decorator(name):
        def decorator_func(func):
            def wrapper_fun(*args, **kwargs):
                func(*args, **kwargs)
            return wrapper_fun
        return decorator_func

看下这个能不能实现我们的想法,首先

是的,这样一解析之后就发现是实现了我们的想法。

改变状态

目前为止我们就已经实现了带参数函数的装饰器,装饰器带参数的功能,接下来还有什么问题呢?好像暂时想不到,那我们就写个例子看看:

    def func():
        """function for test"""
        print "functiong calling"
        print func.__name__
        print func.__doc__

然后执行一遍看下结果:

functiong calling
func
function for test

没什么问题,接着来看下这个:

    def my_decorator():
        def decorator_func(func):
            def wrapper_fun(*args, **kwargs):
                func(*args, **kwargs)
            return wrapper_fun
        return decorator_func

    @my_decorator
    def func():
        """function for test"""
        print "functiong calling"

    print func.__name__
    print func.__doc__

然后运行一遍,看看输出:

    decorator_func
    None

WTF,怎么了,什么情况,我的名字呢?很明显,现在的 func 已经被隐藏了,取而代之的是 decorator_func,那么,加入我们调试代码或者出 bug 了要怎么定位到真正的出问题的 func 呢?那还是需要把 namedoc 这些成员变量找回来的。这里给个坑自己,先不详解:

    from functools import wraps

    def my_decorator():
        def decorator_func(func):
            @wraps(func)
            def wrapper_fun(*args, **kwargs):
                func(*args, **kwargs)
            return wrapper_fun
        return decorator_func

    @my_decorator()
    def func():
        """function for test"""
        print "functiong calling"

    print func.__name__
    print func.__doc__

总结

这篇文章讲解了 Python 装饰器的简单使用以及创建装饰器的方式,创建装饰器的方式。最后还介绍了一下如何保持函数的属性不变。

参考资料