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)。

装饰器的作用

装饰器可以在被装饰的函数前执行预备代码,在它之后执行清理工作。

装饰器有点继承的感觉,它将函数的通用功能剥离出来,成为装饰器。然后在使用时,给需要某通用功能的函数装饰上去,让代码更简洁。

因此,装饰器擅长于记录日志、通过计时来测试代码性能、实现函数的事务处理等。

标签: none

添加新评论