Python 装饰器详解
什么是装饰器
装饰器是一个函数,一个用来包装函数的函数,接收一个函数对象,返回一个新的函数。新函数在原函数的基础上,增加了附加的功能。
1. 最简单的装饰器
def foo():
print("foo() called.")
这是一个简单的函数,如果我们想在函数执行前做点事,可以通过以下装饰器来实现:
def decorator(func):
def wrapper():
print("before %s" % func.__name__)
ret = func()
return ret
return wrapper
装饰器函数 decorator 接收一个函数对象作为参数,然后返回一个内部函数,内部函数的作用是在原函数执行前打印一行说明。这一行可以是其它更复杂的逻辑,当然也可以在原函数执行后做一些操作。
foo = decorator(foo)
foo()
现在执行 foo() 就看到我们要的效果了。
装饰器的实现,归功于 Python 对内部函数的支持,在函数内部创建另外一个函数,同时函数可以作为函数的参数。
2. 使用 Python 语法糖
上面对 foo 的修饰动作,有一种语法支持它。
@decorator
def foo():
print("foo() called.")
即在函数声明前一行加上 @ 标记,这看起来更加简洁,逻辑更紧密。这个语法使得,foo 在声明时,就执行了 foo = decorator(foo)。
类的静态方法(staticmethod)和类方法(classmethod)的实现,通常装饰器使代码更直观。
3. 装饰带参数的函数
其实就是让返回的函数带参数,同时将参数传递到原函数让其调用。
def deco(func):
def wrapper(*args, **kwargs):
print("before %s" % func.__name__)
ret = func(*args, **kwargs)
return ret
return wrapper
以上是通用的不确定参数的例子,如果参数确定,则将 wrapper 的形参定义与 原函数 func 相同即可。
4. 带参数的装饰器
装饰器自身带参数,使得装饰器更灵活。这需要在上面不带参数装饰器的基础上再包裹一层,意思是装饰器先调用自己的参数返回一新的装饰器,这个装饰器和上面的装饰器一样。
def deco(*args2, **kwargs2):
# 这里可以使用参数 args2, kwargs2 处理一些逻辑,外部函数的参数在内部函数均可使用
def _deco(func):
def wrapper(*args, **kwargs):
print("before %s" % func.__name__)
ret = func(*args, **kwargs)
return ret
return wrapper
return _deco
这也是一个通用的带参数的装饰器。使用示例:
def check_session(is_login=False):
def _deco(func):
def wrapper(*args, **kwargs):
if is_login:
# 根据相关条件判断是否已登录,若未登录则抛出错误
pass
return func(*args, **kwargs)
return wrapper
return _deco
@check_session(is_login=True)
def profile(user_id):
pass
这个装饰器通过 is_login 参数来决定是否需要登录,才能获取用户的资料。
5. 多层装饰
对一个函数使用多个装饰器,也是合法的。
@deco2
@deco1
def foo(*args, **kwargs):
pass
它相当于 foo = deco2(deco1(foo))。如果装饰器自身带参数,那么同样是先调用自身参数返回一个新装饰器,再执行上面的多层装饰逻辑。
6. 类也可以被装饰
类的装饰器,返回一个新的类。
def deco(cls):
def show(self):
print(self.__doc__)
cls.show = show
return cls
@deco
class MyObject:
''' class MyObject sample '''
pass
这个装饰器给类增加一个 show() 方法,打印类的文档说明。
7. 包装内部函数
使用上面的装饰器,装饰过的函数,返回的是类似下面的对象:
<function deco.<locals>.wrapper at 0x000000C129C6F158>
<function deco.<locals>._deco.<locals>.wrapper at 0x000000C129C6F268>
foo.__name__ 返回的是 'wrapper' 字符串。
但我们希望 foo 仍像装饰前一样。这时,我们需要使用 functools.wraps 对内部函数再次包装。
from functools import wraps
def deco(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("before %s" % func.__name__)
ret = func(*args, **kwargs)
return ret
return wrapper
它相当于 wrapper = wraps(func)(wrapper)。functools.wraps 装饰器能将被装饰的函数的特殊属性保留下来,这样就可以正常地对 foo 使用反射相关特性。
8. 装饰器也是一种设计模式
装饰模式,是动态地给一个对象添加额外的功能,它比生成子类更灵活。
装饰模式,使任何对象被装饰前后,遵循相同的交互方式。装饰后的对象,可以说是原对象的鸭子类型(duck typing)。
装饰器的作用
装饰器可以在被装饰的函数前执行预备代码,在它之后执行清理工作。
装饰器有点继承的感觉,它将函数的通用功能剥离出来,成为装饰器。然后在使用时,给需要某通用功能的函数装饰上去,让代码更简洁。
因此,装饰器擅长于记录日志、通过计时来测试代码性能、实现函数的事务处理等。