python装饰器与语法糖@

python_decorator
当想为写好的函数添加新功能而又不想或不方便更改原函数时,就可以使用装饰器来解决,如添加计时器,日志记录,权限认证等,有人将此概括为有切面需求的场景。简单来说,就是为已有的对象添加附加功能,就好像在进行“装饰”。

最简单的装饰器

在python中,函数或者说函数名可以作为另一个函数的参数,而装饰器就是以被装饰函数作为参数,对其进行加工修改,并返回修改后的函数名。

# -*- coding=utf-8 -*-
def deco(func):
def wrapper():
func()
return wrapper
def foo():
print 'in foo()'
foo = deco(foo)
foo()
print foo.__name__

为了体现出执行过程,加入打印信息。

print 1
def deco(func):
print 2
def wrapper():
print 3
func()
print 4
return wrapper
print 5
def foo():
print 'run foo()'
foo = deco(foo)
foo()
print foo.__name__
# output
# 1
# 5
# 2
# 4
# 3
# run foo()
# wrapper

在wrapper()函数内,就可以在func()前后添加附加功能,对其进行“装饰”。

加入语法糖@

为了进一步简化代码,python加入了@。下面的@deco等价于foo = deco(foo)。其实也没做多少简化,但使用@能明显感觉到加强了代码的可读性。

print 1
def deco(func):
print 2
def wrapper():
print 3
func()
print 4
return wrapper
print 5
@deco
def foo():
print 'run foo()'
foo()
print foo.__name__
# output
# 1
# 5
# 2
# 4
# 3
# run foo()
# wrapper

被包装函数是有参、有返回值函数

def deco(func):
def wrapper(a,b):
print 'do someting before'
ret = func(a,b)
print 'do something after'
return ret
return wrapper
@deco
def add(x,y):
print 'running add()'
return x+y
print add(2,5)
# do someting before
# running add()
# do something after
# 7

*args, **kwargs

(*args, **kwargs)可接受各种类型的参数

def deco(func):
def wrapper(*args, **kwargs):
print 'do someting before'
ret = func(*args, **kwargs)
print 'do something after'
return ret
return wrapper
@deco
def add1(x,y):
print 'running add()'
return x+y
@deco
def add2(x,y,z):
print 'running add()'
return x+y+z
print add1(2,3)
print add2(2,3,4)
# do someting before
# running add()
# do something after
# 5
# do someting before
# running add()
# do something after
# 9

装饰器带普通参数

与前例相比,需要在外面再加一层包装。

def deco(arg):
def _deco(func):
def wrapper(*args, **kwargs):
print arg
print 'do someting before'
ret = func(*args, **kwargs)
print 'do something after'
return ret
return wrapper
return _deco
@deco('my decorate')
def add1(x,y):
print 'running add()'
return x+y
print add1(2,3)
# my decorate
# do someting before
# running add()
# do something after
# 5

装饰器带类参数

与四的使用方式类似。但要注意的是想要在装饰器中使用的类方法需要声明为静态方法( @staticmethod),因为该类并没有进行实例化。

class test_class:
@staticmethod
def test():
print 'static method test() in test_class'
def deco(cls):
def _deco(func):
def wrapper(*args,**kwargs):
cls.test()
return func(*args,**kwargs)
return wrapper
return _deco
@deco(test_class)
def add(x,y):
print 'running add()'
return x+y
print add(2,3)
# static method test() in test_class
# running add()
# 5

多个装饰器

def deco(arg):
def _deco(func):
def wrapper(*args, **kwargs):
print arg
print 'do someting before'
ret = func(*args, **kwargs)
print 'do something after'
return ret
return wrapper
return _deco
@deco('decorate1')
@deco('decorate2')
def add1(x,y):
print 'running add()'
return x+y
print add1(2,3)
# decorate1
# do someting before
# decorate2
# do someting before
# running add()
# do something after
# do something after
# 5

@functools.wraps()

函数有几个特殊属性比如函数名,在被装饰后,函数名会变成装饰器的包装函数的名字(如下例中的wrapper)。如果你希望使用反射,或者用到了如函数名这样的特殊属性,之前几个例子的装饰器使用方式就会导致意外的结果。functools就可以解决这个问题,将@functools.wraps(func)置于最内层包装函数之前,它能将装饰过的函数的特殊属性保留。

# -*- coding=utf-8 -*-
def deco(func):
def wrapper():
func()
return wrapper
@deco
def foo():
pass
print "foo.__name__:',foo.__name__
# output
# foo.__name__:wrapper #foo.__name__由于@deco被修改
# -*- coding=utf-8 -*-
import functools
def deco(func):
@functools.wraps(func)
def wrapper():
func()
return wrapper
@deco
def foo():
pass
print "foo.__name__:',foo.__name__
# output
# foo.__name__:foo

参考资料

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html

http://www.cnblogs.com/evilliu/p/5546877.html

http://blog.csdn.net/thy38/article/details/4471421

http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

https://www.zhihu.com/question/26930016

------ 本文结束 ------