Python设计模式之装饰器模式(6)

装饰器模式允许在不修改原对象的情况下添加新功能,是结构型模式的一种。Python中,装饰器常用于数据校验、事务处理、缓存、日志等,如`functools.lru_cache`实现缓存。Django框架广泛使用装饰器进行视图功能扩展,如访问控制和缓存。装饰器模式包括定义修饰器和使用修饰器来装饰函数/方法/类,为对象动态添加功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装饰器模式(Decorator Pattern):在不修改原对象结构的前提下,实现扩展对象功能。

通常给一个对象添加新功能有三种方式: - 直接给对象所属的类添加方法。 - 使用『组合』 - 使用『继承』,优先使用组合而非继承。 装饰器模式提供了第四种选择,通过动态改变对象扩展对象功能。其他编程语言通常使用继承实现装饰器装饰器模式,而python内置了装饰器。装饰器有很多用途,比如数据校验,事务处理,缓存,日志等。比如用装饰器实现一个简单的缓存,python3.5自带了functools.lru_cache

1 介绍

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

 

2 适用场景

装饰器模式主要用于给当前类/方法/函数动态增加功能,也叫做添加横切面,常用于以下目的:

  • 数据校验
  • 事务处理(这里的事务类似于数据库事务,意味着要么所有步骤都成功完成,要么事务失败)
  • 缓存
  • 日志
  • 监控
  • 调试
  • 业务规则
  • 压缩
  • 加密

一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看作横切关注点。

使用修饰器模式的另一个常见例子是图形用户界面(Graphical User Interface,GUI)工具集。在一个GUI工具集中,我们希望能够将一些特性,比如边框、阴影、颜色以及滚屏,添加到单个组件/部件。


3 实现步骤

装饰器模式主要步骤在于根据需求定义修饰器,取决于被修饰对象。根据对象的不同,修饰器有很多种实现方式。

步骤一:定义修饰器

步骤二:使用修饰器修饰函数/方法/类

示例代码:

from functools import wraps

# 步骤一:定义修饰器
def memory_cache(fn):
    cached = dict()
    @wraps(fn)
    def inner_func(*args):
        key = str(args)
        if key in cached:
            resp = cached[key]
        else:
            resp = fn(*args)
            cached[key] = resp
        return resp
    return inner_func

# 给函数添加修饰
@memory_cache
def fibonacci(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
    t = Timer('fibonacci(8)', 'from __main__ import fibonacci')
    print(t.timeit())

代码输出:

0.7204972780309618

 

4 代码实践

案例一:给函数添加修饰器,统计函数执行时间,并打印相关日志
import time
import logging
from functools import wraps

logger = logging.getLogger("demo")
"""
步骤一:定义修饰器
"""
def timeit(func):
    @wraps(func)
    def inner_wrapper(self, *args, **kwargs):
        time_start = time.time()
        resp = func(self, *args, **kwargs)
        time_end = time.time()
        logger.warning(' '.join([func.__name__, "time cost:", str(time_end - time_start)]))
        return resp
    return inner_wrapper

"""
步骤二:应用修饰器
"""
class MathDemo(object):
    @timeit
    def sum_data(self, n):
        i = n
        data = 1
        while i > 0:
            data = data * i
            i = i - 1
        return data

if __name__ == '__main__':
    demo = MathDemo()
    res = demo.sum_data(4)
    print(res)

代码输出:

sum_data time cost: 4.0531158447265625e-06
24
案例二:使用修饰器扩展类的功能
通过反省或者重写类定义的某部分来修改它的行为,但是又不希望使用继承或元类的方式.
这种情况可能是类装饰器最好的使用场景了。下面是一个重写了特殊方法 __getattribute__ 的类装饰器, 可以打印日志
def log_getattribute(cls):
    #获取类的属性
    orig_attr = cls.__getattribute__
    def new_attr(self, name):
        print("add new attribute: {}".format(name))
        return orig_attr(self, name)
    cls.__getattribute__ = new_attr
    return cls

@log_getattribute
class A:
    def __init__(self,x):
        self.x = x
    def spam(self):
        pass

a = A("12")
a.spam()

代码输出:

add new attribute: spam

 

更多实践,请参考前面介绍修饰器的博客:

https://blog.youkuaiyun.com/biheyu828/article/details/82532126

 

5 软件例子

Django框架大量地使用修饰器,其中一个例子是视图修饰器。Django的视图(View)修饰器 可用于以下几种用途(请参考网页[t.cn/RqrlJbA])。

  • 限制某些HTTP请求对视图的访问控制特定视图上的缓存行为
  • 按单个视图控制压缩
  • 基于特定HTTP请求头控制缓存

Grok框架也使用修饰器来实现不同的目标,比如下面几种情况。

  • 将一个函数注册为事件订阅者
  • 以特定权限保护一个方法
  • 实现适配器模式

 

参考文献:

https://www.jianshu.com/p/013985a58841

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值