Fork me on GitHub

Python设计模式——修饰器模式

修饰器模式可以在运行时扩展一个对象的功能。

In [ ]:
print('python3')

结构型模式

修饰器模式

修饰器模式通常用于扩展一个对象的功能

给一个对象添加额外的功能有如下选择:

  • 直接将功能添加到对象所属的类
  • 使用组合
  • 使用继承

与继承相比,通常优先选择组合因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。
设计模式为我们提供第四种可选的方法,以支持动态的(运行时)扩展一个对象的功能,这种方法就是修饰器。修饰器模式(Decorator)能够以透明的方式(不会影响其他对象)动态的将功能添加到一个对象中。
在许多其他语言中,使用继承来实现修饰器模式,在Python中可以很方便的使用内置的修饰器特性。

一个Python修饰器就是对Python语法的一个特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。
从实现的角度看,Python修饰器是一个可调用对象(函数、方法、类),接受一个函数对象fin作为输入,并返回另一个函数对象fout。这意味着可以将任何具有这些这些属性的可调用对象当作一个修饰器。

注意:Python修饰器能做的比修饰器模式多得多,其中之一就是修饰器模式。

应用案例

  • 数据校验
  • 事务处理
  • 缓存
  • 日志
  • 监控
  • 调试
  • 业务规则
  • 压缩
  • 加密
  • GUI工具集

以上是横切关注点(cross-cutting concerns)的一些例子。修饰器模式是实现横切关注点的绝佳方法,因为横切关注点通用但不适合使用面向对象编程范式来实现。

实现

下面实现一个memoization修饰器,所有递归函数都因memoization提速。

In [1]:
# coding: utf-8

import functools


def memoize(fn):
    known = dict()

    @functools.wraps(fn)
    def memoizer(*args):
        if args not in known:
            known[args] = fn(*args)
        return known[args]

    return memoizer


@memoize
def nsum(n):
    '''返回前n个数字的和'''
    assert(n >= 0), 'n must be >= 0'
    return 0 if n == 0 else n + nsum(n-1)


@memoize
def fibonacci(n):
    '''返回斐波那契数列的第n个数'''
    assert(n >= 0), 'n must be >= 0'
    return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2)

if __name__ == '__main__':
    from timeit import Timer
    measure = [{'exec': 'fibonacci(100)', 'import': 'fibonacci',
                'func': fibonacci}, {'exec': 'nsum(200)', 'import': 'nsum',
                                     'func': nsum}]
    for m in measure:
        t = Timer('{}'.format(m['exec']), 'from __main__ import \
            {}'.format(m['import']))
        print('name: {}, doc: {}, executing: {}, time: \
            {}'.format(m['func'].__name__, m['func'].__doc__,
                       m['exec'], t.timeit()))
name: fibonacci, doc: 返回斐波那契数列的第n个数, executing: fibonacci(100), time:             0.7618294898420572
name: nsum, doc: 返回前n个数字的和, executing: nsum(200), time:             0.7246086793020368

思考:

  • 在运行时选择是否启用修饰器:加一层封装
  • 多个修饰器如何处理

The END

Comments