在 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
看下这个能不能实现我们的想法,首先
- my_decorator(‘decorator_arg’) =====> decorator_func
- my_decorator(‘decorator_arg’)(test) =====> decorator_func(test) =====> wrapper_fun
- my_decorator(‘decorator_arg’)(test)(‘hello’) =====> wrapper_fun(‘hello’) =====> func(‘hello’)
是的,这样一解析之后就发现是实现了我们的想法。
改变状态
目前为止我们就已经实现了带参数函数的装饰器,装饰器带参数的功能,接下来还有什么问题呢?好像暂时想不到,那我们就写个例子看看:
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 呢?那还是需要把 name 和 doc 这些成员变量找回来的。这里给个坑自己,先不详解:
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 装饰器的简单使用以及创建装饰器的方式,创建装饰器的方式。最后还介绍了一下如何保持函数的属性不变。