当前位置: 首页 > article >正文

66、Python之函数高级:一个装饰器不够用,可以多装饰器buffer叠加

引言

最近有同学关心一个函数只能被一个装饰器装饰吗?能否同时使用多个装饰器进行装饰?又或者,在定义装饰器的时候,我们应该定义一个“瑞士军刀”式的超强装饰器,还是通过多个装饰器实现增强效果的“buffer”叠加。

今天这篇文章,我们就聊一下关于多装饰器的定义及使用。

本文的内容主要有:

1、单一职能原则

2、多装饰器的使用与注意事项

单一职能原则

在介绍多装饰器的使用之前,我想先简单谈一下面向对象设计原则中的“单一职能原则”。

前面我们曾经提及并简单介绍过面向对象设计的5个基本原则,也就是所谓的“SOLID原则”,这些原则的核心理念在于确保软件设计更加稳定及易于扩展和维护。这些原则分别是:

1、单一职能原则(Single Responsibility Principle, SRP)

2、开闭原则(Open / Closed Principle, OCP)

3、里氏替换原则(Liskov Substitution Principle, LSP)

4、接口隔离原则(Interface Segregation Principle, ISP)

5、依赖倒置原则(Dependency Inversion Principle, DIP)

其中,开闭原则我们已经反复提及到,也结合Python相关语法特性的使用,反复实践了开闭原则。

今天,我们来重点聊一下单一职能原则SRP。

SRP的核心思想是:一个类(模块)应该只有一个引起它变化的原因,也就是说一个类只负责一个职责或者功能。

换句话说,一个类应该只有一个变化的理由。将职责分离到不同的类中,可以使得每个类的目标更加明确、职责更加单一,从而提高代码的可维护性和可扩展性。

单一职责的出发点在于:

1、提高代码的可复用性:类的职责更加单一,本质上也就是更加高内聚、低耦合,使得每个单一职责的类可以像是乐高积木一样,在功能扩展时,可以进行快速的组装、复用。

2、提高代码的可维护性:可维护性其实已经蕴含了可读性,类的职责单一,降低了单个类设计、实现上的复杂度。

3、便于进行单元测试:每个类都天然是一个单一职责的单元,单元测试就变得更加简洁、自然。

为了实现单一职责的落地,可以从以下几个方面着手:

1、职责分离:这是该原则的基本,设计之初就将所有的职责做明确的切分。

2、模块化设计:单一职责的原则,必然要求了模块化的设计理念,单一职责所对应的功能封装到独立的模块或者类中,自然就进行了代码的模块化构建。

3、定期代码重构:很多时候,在设计开发过程中,为了快速上线,或者其他的原因,我们会尽量避免过早优化。所以,不可避免的,可能导致部分功能、模块的设计、开发背离了单一职能原则。为了后续的可维护性和可扩展性,应当定期进行多职责耦合的类的识别及代码重构。

虽然,SRP是面向对象的设计原则,但是在结构化设计或者面向过程的开发中,我们在进行函数的定义及开发中,也是应当遵的。

所以,回到开篇提到的装饰器定义的选择,我们是应该定义一个大而全的瑞士军刀式的装饰器,还是应该定义多个单一职责的装饰?从SRP的角度来看,显然应该进行职责的分离,定义多个小而美的装饰器。

多装饰器的使用与注意事项

基于SRP,我们更加倾向于定义多个小而美的装饰器。但是,一个函数可以使用多个装饰器来装饰、增强吗?

其实,只要回顾一下我们之前提到的关于高阶函数、闭包、装饰器的原理,就知道,显然是可以使用多个装饰器的。多个装饰器作用于同一个函数对象,其实就是对原始函数对象的多层套娃,最终还是函数对象。而且,任意一层套娃得到的函数对象,其函数签名与原始函数签名是一致的。

我们前面分别定义了使用缓存和记录日志的装饰器,我们可以看一下两个装饰器放在一起使用的效果。

为了便于查看,我们对日志装饰器进行简化,直接看代码:

1、日志装饰器函数定义 log.py:

# 简化一下日志记录的定义,便于查看
def log(func):
    def wrap(*args, **kwargs):
        print(f'log>> 函数{func.__name__}被调用 参数: {args},{kwargs}')
        try:
            res = func(*args, **kwargs)
            print(f'log>> 函数{func.__name__}被调用 返回值: {res}')
            return res
        except Exception as e:
            print(f'log>> 函数{func.__name__}被调用 发生异常: {e}')

    return wrap

2、缓存装饰器 cache.py:

def cache(func):
    mem = {}

    def wrap(*args):
        res = mem.get(args)
        if not res:
            res = mem[args] = func(*args)
        else:
            print(f"cache>> 参数[{args}]命中缓存")
        return res

    return wrap

3、入口文件进行斐波那契函数的计算:

from log import log
from cache import cache


@log
@cache
def fibonacci(n):
    if n == 1:
        return 0
    if n == 2:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)


if __name__ == '__main__':
    # 查看函数对象信息
    print(fibonacci)
    fibonacci(5)

执行结果:

146cdcaaac0ac55ad03b297c400968ec.jpeg

如果我们交换一下装饰器的顺序,则执行结果会发生变化:

7b37675bcf2b9144b82a20fc80800f57.jpeg

基于上面的执行过程,我们可以得到以下认知:

多个装饰器同时作用于一个函数时,越靠近函数,越先对函数进行封装

@cache
@log
def fibonacci(n):
    pass

等价于:fibonacci = cache(log(fibonacci))

最终得到的函数对象,是最外层装饰器封装的结果。

所以,只要理解了@装饰器名的语法糖背后的实现原理,无论装饰器的使用怎么组合、变换,我们都能轻易理解并掌握。

总结

本文以对“大而全”和“小而美”的设计的疑问为出发点,引出了对单一职责原则的理解与实践,并通过遵循SRP的装饰器设计,实现了多个装饰器对原始函数的buffer效果的叠加,看到更加灵活的组合使用。

感谢您的拨冗阅读,希望对您有所帮助。

2a498f6d72ab54da1dfd79e1ecc1712e.jpeg


http://www.kler.cn/a/299574.html

相关文章:

  • 留学生scratch计算机haskell函数ocaml编程ruby语言prolog作业VB
  • C28.【C++ Cont】顺序表的实现
  • HttpClient学习
  • C++,STL 简介:历史、组成、优势
  • Charles 4.6.7 浏览器网络调试指南:流量过滤与分析(六)
  • 【Matlab高端绘图SCI绘图模板】第006期 对比绘柱状图 (只需替换数据)
  • JQuery:后台接收Json串与对象
  • 828华为云征文| 华为云 Flexus X 实例:引领云计算新时代的柔性算力先锋
  • 文件下载-前端发请求后端返回二进制文件
  • 【Linux】常见指令
  • 在WinForm中使用全局异常捕获处理
  • 【Python报错已解决】 raise JSONDecodeError(“Expecting value“, s, err.value) from None
  • 编程环境常用命令合集
  • 可提示 3D 分割研究里程碑!SAM2Point:SAM2加持泛化任意3D场景、任意提示!
  • 前端文件预览,PDF,word,TXT
  • 项目日志——日志落地模块的设计、实现、测试
  • windows上.chm文件打不开
  • 【Hot100】LeetCode—139. 单词拆分
  • 苹果三款Mac新品十月登场:标配M4系列芯片
  • mysql学习教程,从入门到精通,MySQL WHERE 子句(10)
  • 【区块链通用服务平台及组件】ESGC 基准报告应用 | FISCO BCOS应用案例
  • kubeadm 初始化 k8s 证书过期解决方案
  • 跨部门SOP与统一知识库:打破信息孤岛,促进团队协作
  • Console函数的所有使用方式详解比较
  • Redis 缓存深度解析:穿透、击穿、雪崩与预热的全面解读
  • 如果 Android 手机出现数据丢失,如何在Android上恢复丢失的数据