装饰器和装饰模式

先给出两者的定义:
- 装饰器:装饰器是一个非常著名的设计模式,常常被用于有切面需求的场景。较为经典的有插入日志、性能測试、事务处理等。

装饰器是解决这类问题的绝佳设计。有了装饰器,我们就能够抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲。装饰器的作用就是为已经存在的对象加入额外的功能。
- 装饰模式:在不必改变原类文件和使用继承的情况下。动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰器是python的高级函数应用的一个技巧,能够在不修改对象的前提下增强对象的功能。

这个对象能够是函数,类方法和类属型。看到“装饰”和它的功能,对于设计模式比較熟悉的同学应该会想到装饰模式。假设你如今没有想到装饰器或者对于装饰器没有一个清晰的概念认识,那么就跟我一起来复习一下装饰模式。深入理解装饰模式对于装饰器的理解会非常有帮助。

装饰模式演示样例

这里我们使用java来通过一个简单的场景展示一下装饰模式。

情景:程序员没日没夜的工作能够赚钱,把钱攒起来之后就能够买房买车,迎娶白富美。走上幸福生活。。。
这里我们定义一个能够存钱的接口:CanSaveMoney。程序员类Coder。

public interface CanSaveMoney {
  public void save(int money);
}
class Coder implements CanSaveMoney {

  private int count = 0;

  @Override public void save(int money) {
    count += money;
  }
}
/××
×存钱的方法
××/
public void saveMoney(CanSaveMoney person, int money) {
  person.save(money)
}

情景续:过了一段时间,程序员追到了女神,须要和女神培养感情。

但是我们之前不过留了一个存钱的功能没有取钱的功能,没有问题,程序员都是聪明的,我们存一个负的钱数不就是取钱了麻。

就这样程序员顺利的跟女神培养好了感情。
到了女神管理程序员的收入的时候了,女神识破了程序员的小把戏。如何修复这个漏洞呢?女神是霸道的,女神觉得程序员的一切都是她的,程序员上缴工资卡。一切都经过女神的手。

class Godness implements CanSaveMoney {

  private Coder coder;

  public Godness(Coder coder) {
    this.coder = coder;
  }

  @Override public void save(int money) {
    if (money <= 0) {
      throw new RuntimeException("滚犊子");
    } else {
      coder.save(money);
    }
  }
}

上面就是使用了装饰模式。在不修改原有类的情况下增强类的某一功能。

python 装饰器

python中装饰器是对装饰模式的一个更宽泛的应用,不只能够应用于类,也能应用于函数。类方法和类属性。灵活利用装饰器能够大大提高你的python开发效率。

简单的装饰器演示样例

以下是一些简单的装饰器演示样例, 展示了你能够在函数调用之前或者之后做一些处理。

import functools

# do something before call
def log(fun):
    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        print('begin %s()' % fun.__name__)
        return fun(*args, **kwargs)

    return wrapper

# do something before and after call
def log2(fun):
    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        print('begin call %s()' % fun.__name__)
        f = fun(*args, **kwargs)
        print('end call')
        return f

    return wrapper


# decorator with param
def log_tag(tag):
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('%s %s()' % (tag, func.__name__))
            return func(*args, **kwargs)

        return wrapper

    return log

#装饰器类,适用于须要保存一些多次调用的信息。如函数调用的次数
class LogClass(object):
    def __init__(self, f):
        self.f = f
        self.count = 0
        # 复制原函数的属性
        for n in set(dir(f)) - set(dir(self)):
            setattr(self, n, getattr(f, n))

    def __call__(self, *args):
        self.count += 1
        print('called times: %s' % self.count)
        return self.f(*args)

    def __repr__(self):
        return self.f

@log_tag("execute")
def print_num(n=0):
    print('num is %s' % n)

if __name__ == '__main__':
    for n in range(1, 4):
        print_num(n)
    print('%s' % print_num.__name__)
关于@functools.wraps()

@functools.wraps()是functools模块中一个非常实用的装饰器,它的作用是把原函数的属性拷贝到装饰过的函数中。函数也是对象,所以函数的属性这也概念不难理解,比如你能够使用pirnt('%s' % print_num.__name__)来打印出函数的名称属性。这里重点解释装饰过的函数并不是原函数。
上面的代码片断最后我们打印出了函数的名称属性。假设我们把装饰器中的@functools.wraps()这一行凝视掉之后。再运行打印,打印出的结果不会再是print_numm了。而是wrapper。弄清晰原因你就理解了@functools.wraps()的作用。

为什么装饰过的函数的print_num.__name__改变了?

上面我们使用@log的形式来使用装饰器装饰函数。这在python里面叫装饰器语法。不使用装饰器语法的的形式是这种

print_num = log(print_num)
print_num(n)

这样我们就理解了为什么装饰过后的函数不再是原来的函数了。上面代码段中装饰器类初始化代码中获取设置原函数属性的作用相似于@functools.wraps()的作用。