如何不饶的理解Python里装饰器?

@xxx放在函数/类 定义上面,就是Python里的装饰器语法。先介绍什么是装饰器,再介绍语法含义。

装饰器

一种经典设计模式,可以理解为套娃。把一个东西包裹一层,再暴露给别人,这个包裹的装置就是装饰器。

Python里的装饰器

前面提到, @xxx放在类/函数定义上就是在Python里应用了装饰器了。它的语法糖拆开如下(举例):

@xxx
def original_fn(a, b):
    return a + b

# ==>

original_fn = xxx(original_fn)

如果你要自己写装饰器,xxx就得满足这样的性质:

  1. 接受1个参数a,返回1个对象A; a就是被装饰的对象,A就是对a包裹的结果。
  2. A本身接受的参数应该与a匹配;因为A在后面就替代a了;

上面就是对装饰器的理解,下面再说几个常见的东西:

1. 带参数的装饰器?

就是这样的:

@xxx(a, b)
def original_fn(c, d):
    return c + d

这个什么? 搞清楚很简单,xxx(a, b) 本身是一个函数调用!这里xxx不是一个装饰器,xxx(a, b)函数调用 返回的才是一个装饰器!把它看作装饰器的构造器比较合适。

如果要全部用函数来定义这样的xxx,这个xxx估计是一个3层套娃……

2. 用类实现一个装饰器?

常见装饰器都是拿函数实现的,但是也可以用类来做。只需要满足上面的 @语法的语法糖就行。

一般地,用类来做装饰器,类的构造函数就接受被装饰对象,类的__call__就接受与被装饰对象匹配的参数。

用类做装饰器,更加直观,稍微没那么绕,也让人更清楚@的本质——并没有明确的范式,满足语法糖扩展就行了。

3. 装饰器去装饰一个类?

跟装饰函数一个道理。返回的对象和该类接口兼容就行。

4. functools.wraps 做什么、怎么做?

对象被装饰之后,本质上已经是一个新的对象了,也就不再保留原被装饰对象的属性了,如__doc__, __name__等, 这个显然是符合科学的。

然而有时我们偏偏想保留原来被装饰对象的__doc__等属性——不可以违背科学,那就再装饰一下吧!

我们要知道,对象的__doc__等是可以赋值修改的! 我们把装饰后的对象,修改其__doc__等属性为原对象的属性,不就 达到目的了吗。这就是functools.wraps做的事情。它也是依赖装饰器语法的,真的套娃,难受。

它是为装饰器服务的,与被装饰对象无关。

实际实现似乎比较复杂,有封装,逻辑实现应该是如下?

def wraps(original_fn):
    """it it actual a wrapper creator
    """
    def wraps_decorator(original_wraps):
        """a in-out decorator
        """
        def nest_wraps(*args, **kargs):
            return original_wraps(*args, **kwargs)
        nest_wraps.__doc__ == orignal_fn.__doc__
        # ...
        return nest_wraps
    return wraps_decorator


def decorator(original_fn):
    @wraps(original_fn)
    def actual_wrapper(*args, **kwargs):
        # ...
        original_fn(*args, **kwargs)
        # ..
    return actual_wrapper

==>

# 把decorator内部的装饰器展开,其实就是

actual_wrapper = wraps(original_fn)(actual_wrapper)
            = wraps_decorator(actual_wrapper)
            = nest_wraps
# => 大概是这样的吧,这样包装后actual_wrapper就有了 original_fn 的属性了。我自己又晕了……