Python 设计模式初探

本文章是在阅读精通Python设计模式(中文版)(https://book.douban.com/subject/26829015/),以及阅读 Mask R-CNN 第三方Tensorflow代码的基础上记录得到。

豆瓣上似乎对该书的评价不高,这里仅以此书为基础,试图理解Python中常见的设计模式,并有效看懂相关代码。

01 工厂模式

1.1 实际需求

假设我希望对多种 CNN model (例如 "AlexNet" 和 "VGGNet")的分类性能进行测试,因此我可能需要去实现不同的 CNN model的代码,并在主函数分别进行调用测试。

1.2 直接方式

我首先采用 Tensorflow(之后简写为TF) 编写多种类型的 CNN model 对应的 class。例如, "AlexNet" 对应的class是 AlexCNN,"VGGNet" 对应的class是 VGGCNN。然后在 main 函数中直接实例化不同类别的class,然后依次进行不同 网络 instance 的"训练" 和 "测试",最后得到每种CNN model的分类性能。

下面给出这种方式的代码示例:

# 多个类别的网络class 定义class AlexCNN: def __init__(*args, **kwargs): # do initializationclass VGGCNN: def __init__(*args, **kwargs): # do initializationif __name__ == "__main__": A = AlexCNN() B = VGGCNN() # training and testing, etc

注意,这里的方式是将各个网络 class 的实例化放到主函数中进行。这样操作会使得内部的class暴露给外部。

我们希望采用一种新的方式,直接提出需求(给出网络类别),然后某函数返回给我一个实例化好了的类,我也不需要在main函数中去显式调用类别实例化了。

1.3 工厂设计模式

在工厂设计模式中,客户(我)只希望按照自己的要求(CNN model 类别)获得相应的商品(对应类别的 instance),而不关心商品是如何生成的。该设计模式背后的思想是希望简化对象的创建。

下面给出工厂设计模式的代码示例:

# 多个类别的网络class 定义class AlexCNN:
    def __init__(*args, **kwargs):
        # do initializationclass VGGCNN:
    def __init__(*args, **kwargs):
        # do initializationif __name__ == "__main__":
    A   = AlexCNN()
    B   = VGGCNN()    # training and testing, etc

与直接在main函数中实例化的方式相比,通过基于函数的实现,更易于追踪创建了哪些对象。通过将创建对象的代码和使用对象的代码解耦,工厂设计模式能够降低应用维护的复杂度。

02

装饰器模式

2.1 实际需求

一个简单的例子 假设现在有多个函数,有的函数是递归的,我希望对这些函数进行微修改,然后打印输出系统在执行函数时,进行的调用过程以及消耗的时间

以下面两个函数为例,

# 非递归的import timedef snooze(seconds): time.sleep(seconds)# 递归的def factorial(n): return 1 if n < 2 else n * factorial(n-1)

2.2 直接方式

2.2.1 直接修改每个函数内部的实现

当函数很少的时候,可以直接对函数内部进行修改,使其达到打印输出的功能

如下这样改造,

# 多个类别的网络class 定义class AlexCNN:
    def __init__(*args, **kwargs):
        # do initializationclass VGGCNN:
    def __init__(*args, **kwargs):
        # do initializationdef CNN_factory(name, *args, **kwargs):
    if str(name) == "AlexNet":        return AlexCNN(args, kwargs)    if str(name) == "VGGNet":        return VGGCNN(args, kwargs)if __name__ == "__main__":
    A   = CNN_factory("AlexNet")
    B   = CNN_factory("VGGNet")    # training and testing, etc

这种方式存在的问题是,原本的函数内部结构被修改得七零八落,打印输出的功能与函数本身的计算功能耦合在了一起,如果要改变打印方式,势必需要重新修改函数内部的打印输出的实现方式。增加了后续的维护成本。

2.2.2 传入打印函数

既然在函数内部直接编写打印代码不太合适,那就将打印函数以参数的形式传入。当需要修改打印输出模式时,直接修改打印函数的形式即可。相当于将打印函数与计算函数解耦。

如下这样改造,

import timedef printFunc1(elapsedTime, funcName, inputParam, outputParam):     
print("[%s] %s(%s) -> %s".format(elapsedTime, funcName, str(inputParam), str(outputParam)))
def printFunc2(*args, **kwargs):     
passdef snooze(seconds, printFunc):     
start_time  = time.time()     
time.sleep(seconds)     
end_time    = time.time()     
printFunc(elapsedTime="[%f] s".format(end_time - start_time), funcName="snooze", inputParam=str(seconds), outputParam=str(None))
def factorial(n, printFunc):     
start_time  = time.time()     
result      = 1 if n < 2 else n * factorial(n-1)     
end_time    = time.time()     
printFunc(elapsedTime="[%f] s".format(end_time - start_time), funcName="factorial", inputParam=str(n), outputParam=str(result))   
return result

这种方式相比上一种方法,耦合性得以降低,但如果现在我想调用

不带计时作用的函数,可能又得重新修改这个函数了。那能不能想一种方法,技能获得计时效果,又可以保证原来的函数不受到破坏。

2.3 装饰器设计模式

2.3.1 什么是装饰器设计模式

在已有函数的基础上,我们希望对一个对象添加额外的功能。那么有以下几种方法:

  • 如果合理,直接将功能添加到对象所属的类(例如,添加一个新的方法)
  • 使用组合
  • 使用继承

而装饰器模式则提供了第四种方法,以支持动态地(runtime,运行时)扩展一个对象的功能。装饰器(decorator)模式能够以透明的方式(不会影响其它对象)动态地将功能添加到一个对象中。

2.3.2 Python中的装饰器

很多编程语言中都使用子类化(继承)来实现装饰器模式。而Python中内置了装饰器特性。一个Python装饰器就是对Python语法的一个特定改变,用于扩展一个类,方法或函数的行为,而无需使用继承。

例如,假如有个名为decorate的装饰器:

# 用decorate装饰器来对以下的函数进行装饰@decoratedef target(): print( "running target()")# 实际上,上述写法是一种语法糖,它等价于def target(): print( "running target()" ) target = decorate(target)

可以看到,装饰器是可以调用的对象,其参数是另一个函数(即被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。

2.3.3 如何解决上述问题

直接上代码,

import timedef clock(func):
    def clocked(*args):
        t0  = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))        return result    return clocked@clockdef snooze(seconds):
    time.sleep(seconds)@clockdef factorial(n):
    return 1 if n < 2 else n * factorial(n-1)# 测试一下if __name__ == "__main__":
    print('*' * 40, 'Calling snooze(.123)')
    snooze(.123)
    print('*' * 40, 'Calling factorial(6)')
    print('6! =', factorial(6))

可以看到,装饰器clock以函数为输入,输出的也是函数,但是中间加了额外的代码逻辑。

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2018-01-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI研习社

深度学习对话系统实战篇 -- 简单 chatbot 代码实现

本文的代码都可以到我的 github 中下载:https://github.com/lc222/seq2seq_chatbot 前面几篇文章我们已经介绍了 s...

8988
来自专栏小樱的经验随笔

Vijos P1131 最小公倍数和最大公约数问题【暴力】

一元三次方程求解 描述 有形如:ax^3+bx^2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三...

3325
来自专栏应兆康的专栏

100个Numpy练习【3】

翻译:YingJoy 网址: https://www.yingjoy.cn/ 来源: https://github.com/rougier/numpy-100...

42410
来自专栏python3

python语句-for

722
来自专栏专知

【附源码】TensorFlow动态图(Eager模式)的那些神坑

导读:TensorFlow的动态图(Eager模式)为TensorFlow提供了Pythonic的API,让开发者可以像使用PyTorch一样使用TensorF...

922
来自专栏Python中文社区

实现属于自己的TensorFlow(一) - 计算图与前向传播

前言 前段时间因为课题需要使用了一段时间TensorFlow,感觉这种框架很有意思,除了可以搭建复杂的神经网络,也可以优化其他自己需要的计算模型,所以一直想自...

4237
来自专栏深度学习之tensorflow实战篇

tensorflow(一)windows 10 python3.6安装tensorflow1.4与基本概念解读

一.安装 目前用了tensorflow、deeplearning4j两个深度学习框架, tensorflow 之前一直支持到python 3.5,目前以更...

4124
来自专栏章鱼的慢慢技术路

Direct3D 11 Tutorial 7:Texture Mapping and Constant Buffers_Direct3D 11 教程7:纹理映射和常量缓冲区

在上一个教程中,我们为项目引入了照明。 现在我们将通过向我们的立方体添加纹理来构建它。 此外,我们将介绍常量缓冲区的概念,并解释如何使用缓冲区通过最小化带宽使用...

954
来自专栏Script Boy (CN-SIMO)

Java中随机数的产生方式与原理

查阅随机数相关资料,特做整理 首先说一下java中产生随机数的几种方式 在j2se中我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数...

2640
来自专栏青玉伏案

算法与数据结构(五) 普利姆与克鲁斯卡尔的最小生成树(Swift版)

上篇博客我们聊了图的物理存储结构邻接矩阵和邻接链表,然后在此基础上给出了图的深度优先搜索和广度优先搜索。本篇博客就在上一篇博客的基础上进行延伸,也是关于图的。今...

2517

扫码关注云+社区