python的装饰器与设计模式中的装饰器模式

简介: python的装饰器与设计模式中的装饰器模式

相信很多人在初次接触python中的装饰器时,会跟我一样有个疑问,这跟设计模式中的装饰器模式有什么区别吗?本质上是一样的,都是对现有对象,包括函数或者类的一种扩展。这篇文档将进行对比分析。

python的装饰器

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。

使用一个性能测试的例子来说明。当我们需要测试一段训练或者推理的时长时,可能会写出类似这样的代码,直接在一个训练函数里加入时间统计

def train(X, y):
    start = time.time()
    knn = KNeighborsTimeSeriesClassifier(n_neighbors=2)
    knn.fit(X, y)
    end  = time.time()
    train_time = end - start
    print('train time cost : %.5f sec' %train_time)
    return knn

这是一段真实的代码片段,选自在某次训练任务过程中。当然如果不知道python的装饰器,在这个比较简短的函数体内,加入三行代码,其实也无伤大雅。但是既然山在那里,就要去攀登呀,因此强行上装饰器。

def getDuration(func):
    def wrapper(*args, **kwargs):
        print("%s is running" % func.__name__)
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        train_time = end - start
        print('train time cost : %.5f sec' %train_time)
        return result
    return wrapper
@getDuration
def train(X, y):
    # start = time.time()
    knn = KNeighborsTimeSeriesClassifier(n_neighbors=2)
    knn.fit(X, y)
    # end  = time.time()
    # train_time = end - start
    # print('train time cost : %.5f sec' %train_time)
    return knn

在train上方加入@+函数名,即将train函数加上一层装饰器。train函数中原本记录时间的start和end,移到wrapper中。train的函数通过wrapper中的*args、**kwargs转发。

面向对象的装饰器模式

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。其结构图为

这个通用的结构看起来略显抽象,结合一个咖啡豆的例子来说明,其UML图为:

咖啡是由咖啡豆制作来的。而咖啡又根据糖分含量(或奶含量?)的比例不同分为美式、拿铁、摩卡、卡布奇洛等等。那不同的咖啡当然可以有咖啡这个类继承而来,但更灵活的方式是用装饰器模式。这里贴出这几个类的设计实现。

// 咖啡豆,抽象类,定义接口,可以显示咖啡类型和价格
class CoffeeBean
{
public:
  virtual void ShowCoffeeName() = 0;
  virtual void ShowPrice() = 0;
public:
  std::string m_sCoffeeName;
  int m_iPrice;
};
//对咖啡豆类的一份实现
class Coffee:public CoffeeBean
{
public:
  Coffee(std::string name,int price)
  {
    m_sCoffeeName = name;
    m_iPrice = price;
  }
  ~Coffee() {}
  void ShowCoffeeName()
  {
    std::cout << "CoffeeBean name:" << m_sCoffeeName << std::endl;
  }
  void ShowPrice()
  {
    std::cout << "CoffeeBean Price:" << m_iPrice << std::endl;
  }
};
// 对咖啡豆抽象类的扩展,这个扩展类相当于在caffeebean类型上加上一个装饰器
// 的效果,不同的装饰器成为美式、拿铁和摩卡
class ExtendCoffee :public CoffeeBean
{
public:
  ExtendCoffee(CoffeeBean* pBean)
  {
    m_pBean = pBean;
  }
  ~ExtendCoffee(){}
  virtual void ShowCoffeeName() = 0;
  virtual void ShowPrice() = 0;
protected:
  CoffeeBean* m_pBean;
};
// 美式的实现版本(通过装饰器的方式)
class Americano :public ExtendCoffee
{
public:
  Americano(CoffeeBean* pBean):ExtendCoffee(pBean){}
  ~Americano() {}
  void ShowCoffeeName()
  {
    std::cout << "I am Americano Coffee,Coffee name:" << m_pBean->m_sCoffeeName + " from American" << std::endl;
  }
  void ShowPrice()
  {
    m_pBean->m_iPrice = 48;
    std::cout << "Americano Coffee price:" << m_pBean->m_iPrice << std::endl;
  }
};
// 拿铁的实现版本(通过装饰器的方式)
class Latte :public ExtendCoffee
{
public:
  Latte(CoffeeBean* pBean) :ExtendCoffee(pBean) {}
  ~Latte() {}
  void ShowCoffeeName()
  {
    std::cout << "I am Latte Coffee,Coffee name:" << m_pBean->m_sCoffeeName + " from Italy" << std::endl;
  }
  void ShowPrice()
  {
    m_pBean->m_iPrice = 58;
    std::cout << "Latte Coffee price:" << m_pBean->m_iPrice << std::endl;
  }
};
// 摩卡的实现版本(通过装饰器的方式)
class Mocha :public ExtendCoffee
{
public:
  Mocha(CoffeeBean* pBean) :ExtendCoffee(pBean) {}
  ~Mocha() {}
  void ShowCoffeeName()
  {
    std::cout << "I am Mocha Coffee,Coffee name:" << m_pBean->m_sCoffeeName + " from Franch" << std::endl;
  }
  void ShowPrice()
  {
    m_pBean->m_iPrice = 68;
    std::cout << "Mocha Coffee price:" << m_pBean->m_iPrice << std::endl;
  }
};

这里的ExtendCoffee相当于抽象装饰,由此基础上实现了不同的装饰效果,即不同的咖啡类型。

优点

  • 不改动原有代码,动态增加功能。
  • 对象间不会相互依赖、松耦合。
  • 符合开闭原则,扩展性好,便于维护。

缺点

  • 装饰器环节过多的话,导致装饰器类膨胀。
  • 装饰器层层嵌套比较复杂,可能导致排查问题流程繁琐。
参考文档

Python 函数装饰器

理解 Python 装饰器看这一篇就够了

C++装饰器模式

设计模式装饰器模式

相关文章
|
8天前
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
17 3
|
15天前
|
缓存 监控 程序员
Python中的装饰器是一种特殊类型的声明,它允许程序员在不修改原有函数或类代码的基础上,通过在函数定义前添加额外的逻辑来增强或修改其行为。
【6月更文挑战第30天】Python装饰器是无侵入性地增强函数行为的工具,它们是接收函数并返回新函数的可调用对象。通过`@decorator`语法,可以在不修改原函数代码的情况下,添加如日志、性能监控等功能。装饰器促进代码复用、模块化,并保持源代码整洁。例如,`timer_decorator`能测量函数运行时间,展示其灵活性。
18 0
|
8天前
|
程序员 Python
从零到一,彻底掌握Python闭包与装饰器的精髓,成为编程界的隐藏Boss
【7月更文挑战第7天】探索Python编程的两大基石:闭包与装饰器。闭包是内部函数记住外部作用域的变量,如`make_multiplier_of`返回的`multiplier`,它保持对`n`的引用。装饰器则是函数工厂,接收函数并返回新函数,如`my_decorator`,它在不改变原函数代码的情况下添加日志功能。掌握这些,让代码更优雅,效率更高,助你成为编程高手。
16 3
|
8天前
|
程序员 Python
程序员必看!Python闭包与装饰器的高级应用,让你的代码更优雅、更强大
【7月更文挑战第7天】Python中的闭包和装饰器是高级特性,用于增强代码功能。闭包是内部函数记住外部作用域的变量,常用于动态函数和函数工厂。示例展示了`make_multiplier_of`返回记住n值的`multiplier`闭包。装饰器则是接收函数并返回新函数的函数,用于不修改原函数代码就添加功能。`my_decorator`装饰器通过`@`语法应用到`say_hello`函数上,展示了在调用前后添加额外行为的能力。这两种技术能提升代码的优雅性和效率。
16 3
|
8天前
|
Python
Python编程实战:利用闭包与装饰器优化日志记录功能
【7月更文挑战第7天】Python的闭包和装饰器简化了日志记录。通过定义如`log_decorator`的装饰器,可以在不修改原函数代码的情况下添加日志功能。当@log_decorator用于`add(x, y)`函数时,调用时自动记录日志。进一步,`timestamp_log_decorator`展示了如何创建特定功能的装饰器,如添加时间戳。这些技术减少了代码冗余,提高了代码的可维护性。
15 1
|
8天前
|
Python
Python黑魔法揭秘:闭包与装饰器的高级玩法,让你代码飞起来
【7月更文挑战第7天】Python的闭包和装饰器是提升代码效率的神器。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建。示例中,`make_multiplier_of`返回一个保留`n`值的闭包。装饰器则是一个接收函数并返回新函数的函数,用于在不修改原函数情况下添加功能,如日志或性能追踪。`@my_decorator`装饰的`say_hello`函数在执行时会自动加上额外操作。掌握这两者,能让Python代码更优雅、强大。**
|
11天前
|
自然语言处理 Python
从菜鸟到大神,一篇文章带你玩转Python闭包与装饰器的深度应用
【7月更文挑战第4天】Python中的闭包和装饰器是增强代码优雅性的关键特性。闭包是能访问外部作用域变量的内部函数,如示例中的`inner_function`。装饰器则是接收函数并返回新函数的函数,用于扩展功能,如`my_decorator`。装饰器可与闭包结合,如`repeat`装饰器,它使用闭包记住参数并在调用时重复执行原函数。这些概念提升了代码复用和可维护性。
|
19天前
|
Python
Python深入讲解系列之装饰器
Python深入讲解系列之装饰器
11 1
|
1天前
|
缓存 测试技术 Python
Python中的装饰器:优雅地增强函数功能
在Python编程中,装饰器是一种强大的工具,它能够在不改变函数本身的情况下,动态地增强其功能。本文将深入探讨装饰器的工作原理、常见用法以及如何利用装饰器提高代码的可重用性和可维护性。
|
3天前
|
UED Python
Python装饰器怎么做重试机制
**使用Python装饰器实现的重试机制**简化了对可能出现临时故障的函数的处理,增强系统稳定性和用户体验。文中提供了一个简单的装饰器示例,允许在达到最大重试次数前,按设定间隔自动重试失败的函数调用。这种机制在分布式系统、网络通信中尤为重要,可应对网络波动、资源紧张等问题,避免服务中断。通过添加`@retry`装饰器,无需大量修改代码即可为函数添加重试功能。