Python 设计模式初探

Python 设计模式初探

本博客是在阅读精通Python设计模式(中文版),以及阅读 Mask R-CNN 第三方Tensorflow代码的基础上记录得到。
豆瓣上似乎对该书的评价不高,这里仅以此书为基础,试图理解Python中常见的设计模式,并有效看懂相关代码

1. 工厂模式

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 initialization
class VGGCNN:
    def __init__(*args, **kwargs):
        # do initialization
if __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 initialization
class VGGCNN:
    def __init__(*args, **kwargs):
        # do initialization
def 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

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

2. 装饰器模式

2.1 实际需求

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

以下面两个函数为例,

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

2.2 直接方式

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

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

import time
def snooze(seconds, needPrint=False)
    start_time  = time.time()
    time.sleep(seconds)
    end_time    = time.time()
    # 新添加的
    if needPrint:
        print("[%f s] snooze(%s) -> None".format(end_time - start_time, str(seconds)))
def factorial(n, needPrint=False):
    start_time  = time.time()
    result      = 1 if n < 2 else n * factorial(n-1)
    end_time    = time.time()
    if needPrint:
        print("[%f s] snooze(%s) -> %s".format(end_time - start_time, str(n), str(result)))
    return result

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

2.2.2 传入打印函数

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

import time
def printFunc1(elapsedTime, funcName, inputParam, outputParam):
    print("[%s] %s(%s) -> %s".format(elapsedTime, funcName, str(inputParam), str(outputParam)))
def printFunc2(*args, **kwargs):
    pass
def 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装饰器来对以下的函数进行装饰
@decorate
def target():
    print( "running target()")

# 实际上,上述写法是一种语法糖,它等价于
def target():
    print( "running target()" )
target  = decorate(target)

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

2.3.3 如何解决上述问题

直接上代码,

import time
def 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
@clock
def snooze(seconds):
    time.sleep(seconds)
@clock
def 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以函数为输入,输出的也是函数,但是中间加了额外的代码逻辑

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,545评论 1 118
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,436评论 6 428
  • 已经连续一年多每天早起晨读了。 第一次去社团晨读,是因为听了精品课,然后参加晨读。去的第一天就让我觉得好温暖,好喜...
    筱雨泽阅读 442评论 0 0
  • 前几天,在微信上看到一篇文章,说只有颜值高又有钱的吃货才称得上是真正的吃货。我想这人写文章之前一定没有百度过吃货,...
    艾普萝妮阅读 235评论 0 1