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

  1. @staticmethod
  2. def func():
  3. ... ...

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

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

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

  1. func = staticmethod(func)

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

  1. class Foo(object):
  2. def func():
  3. ... ...
  4. func = staticmethod(func)
  5. Foo.func()

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

  1. class Foo(object):
  2. @staticmethod
  3. def func():
  4. ... ...
  5. Foo.func()

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

  1. @wrap
  2. def func():
  3. ... ...
  4. func()

等价于:

  1. wrap(func)()

举个栗子

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

  1. def my_decorator(func):
  2. def wrapper_fun():
  3. begin_time = time()
  4. func()
  5. end_time = time()
  6. print int(end_time - begin_time)
  7. return wrapper_fun
  8. @my_decorator
  9. def test():
  10. print "test func run"
  11. sleep(3)
  12. test()

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

带参数的函数

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

  1. def my_decorator(func):
  2. def wrapper_fun():
  3. begin_time = time()
  4. func()
  5. end_time = time()
  6. print int(end_time - begin_time)
  7. return wrapper_fun
  8. @my_decorator
  9. def test(arg):
  10. print "test func run with arg: {}".format(arg)
  11. test('hello')

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

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

  1. def my_decorator(func):
  2. def wrapper_fun(*args, **kwargs):
  3. print "decorator begin"
  4. func(*args, **kwargs)
  5. print "decorator end"
  6. return wrapper_fun
  7. @my_decorator
  8. def test(arg):
  9. print "test func run with arg: {}".format(arg)
  10. test('hello')

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

  1. decorator begin
  2. test func run with arg: hello
  3. decorator end

装饰器带参数

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

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

  1. def my_decorator(func, *oargs, **okwargs):
  2. def wrapper_fun(*iargs, **ikwargs):
  3. print "decorator begin"
  4. func(*iargs, **ikwargs)
  5. print "decorator end"
  6. return wrapper_fun

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

  1. my_decorator('decorator_arg')(test)('hello')

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

  1. def my_decorator(name):
  2. def decorator_func(func):
  3. def wrapper_fun(*args, **kwargs):
  4. func(*args, **kwargs)
  5. return wrapper_fun
  6. return decorator_func

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

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

改变状态

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

  1. def func():
  2. """function for test"""
  3. print "functiong calling"
  4. print func.__name__
  5. print func.__doc__

然后执行一遍看下结果:

  1. functiong calling
  2. func
  3. function for test

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

  1. def my_decorator():
  2. def decorator_func(func):
  3. def wrapper_fun(*args, **kwargs):
  4. func(*args, **kwargs)
  5. return wrapper_fun
  6. return decorator_func
  7. @my_decorator
  8. def func():
  9. """function for test"""
  10. print "functiong calling"
  11. print func.__name__
  12. print func.__doc__

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

  1. decorator_func
  2. None

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

  1. from functools import wraps
  2. def my_decorator():
  3. def decorator_func(func):
  4. @wraps(func)
  5. def wrapper_fun(*args, **kwargs):
  6. func(*args, **kwargs)
  7. return wrapper_fun
  8. return decorator_func
  9. @my_decorator()
  10. def func():
  11. """function for test"""
  12. print "functiong calling"
  13. print func.__name__
  14. print func.__doc__

总结

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

参考资料