背景###

在构建测试用例集时,常常需要编写一些函数,这些函数接受基本相同的参数,仅有一个参数有所差异,并且处理模式也非常相同。可以使用Python闭包来定义模板函数,然后通过参数调节来自动化生产不同的函数。

示例###

看如下代码:

def commonGenerate(startTime, endTime, field, values):
reqs = []
for val in values:
requestId = str(startTime) + "_" + str(endTime) + "_" + val
baseReq = json.loads(baseExportReqStr)
baseReq['osp']['start_time'] = startTime
baseReq['osp']['end_time'] = endTime
baseReq['osp'][field] = [val]
baseReq['request_id'] = requestId
reqs.append(json.dumps(baseReq))
return reqs def generateReqByState(startTime, endTime):
states = ["S1", "S2", "S3", "S4", "S5", "S6"]
return commonGenerate(startTime, endTime, 'state', states) def generateReqByOrderType(startTime, endTime):
orderTypes = ["T1", "T2", "T3"]
return commonGenerate(startTime, endTime, 'type', orderTypes) def generateReqByExpressType(startTime, endTime):
expressTypes = ["E1", "E2", "E3"]
return commonGenerate(startTime, endTime, 'expr_type', expressTypes) def generateReqByFeedback(startTime, endTime):
feedbacks = ["F1", "F2"]
return commonGenerate(startTime, endTime, 'fb', feedbacks) def getGenerateFuncs():
gvars = globals()
return [ gvars[var] for var in gvars if var.startswith('generateReq') ] caseGenerateFuncs = getGenerateFuncs()
print caseGenerateFuncs

这里已经抽离出通用函数 commonGenerate ,在此基础上定义了多个 generateReqByXXX ,这些函数的模式基本相同,无非是传一个字段名及值列表,然后生成一个不一样的函数。 那么,是否可以做成可配置化呢: 只要给定一个 map[字段名,值列表], 就能自动生成这些函数 ?

使用闭包可以达到这个目标。见如下代码所示:

def commonGenerator(startTime, endTime, field, values):
def generateReqInner(startTime, endTime):
reqs = []
for val in values:
requestId = str(startTime) + "_" + str(endTime) + "_" + val
baseReq = json.loads(baseExportReqStr)
baseReq['osp']['start_time'] = startTime
baseReq['osp']['end_time'] = endTime
baseReq['osp'][field] = [val]
baseReq['request_id'] = requestId
reqs.append(json.dumps(baseReq))
return reqs
return generateReqInner def generateGenerators(startTime, endTime, configs):
gvars = globals()
for (field, values) in configs.iteritems():
gvars['generateReqBy' + field] = commonGenerator(startTime, endTime, field, values) configs = {"state": ["S1", "S2", "S3", "S4", "S5", "S6"], \
"type": ["T1", "T2", "T3"], \
"expr_type": ["E1", "E2", "E3"], \
"fb": ["F1", "F2"]
} def getGenerateFuncs():
gvars = globals()
return [ gvars[var] for var in gvars if var.startswith('generateReq') ] generateGenerators(startTime, endTime, configs)
caseGenerateFuncs = getGenerateFuncs()
print caseGenerateFuncs

这里函数 commonGenerator 对 commonGenerate 做了一点改动,不再直接返回值列表,而是根据不同的参数返回一个处理不同的函数,这个函数会返回值列表; 然后 generateGenerators 根据指定配置 configs, 调用 commonGenerator 来批量生产generateReqByXXX函数。妙不妙,生产函数的函数 !

闭包###

理解####

按维基的解释: 闭包是引用了自由变量的函数。在例子中,闭包就是 generateReqInner , 引用了传入的自由变量 field, values, 从而在 commonGenerator 调用结束之后,generateReqInner 依然存在能够被访问,且功能效果等同于 generateReqInner(startTime, endTime, field, values) 。

知乎上有句话很有启发性: 闭包就是一种特殊性质的数据,只不过这种数据恰好是携带了数据的代码块,是一个潜伏起来的随时待执行的完整的对象体(数据-行为绑定的执行体)。从这个角度来说,也可以理解闭包的实现:

  • 要件一: 闭包必定存在于一个封闭的作用域 D; 例子中这个 D 就是 commonGenerator 函数的作用域;
  • 要件二: 处于封闭作用域的代码块访问了在代码块作用域之外的封闭作用域里的自由变量。

若只访问自身里的参数及局部变量,就是普通代码块;当封闭作用域结束后,里面的一切都会被销毁; 如果这个代码块,除了访问自身的参数及局部变量,还访问在它之外的封闭作用域里的变量,那么,这个普通代码块就升级为闭包,其访问的自由变量和这个代码块将会共同保存并独立于封闭作用域的存在; 当封闭作用域结束后,这个闭包不会一同消亡,而是继续独立存在。 例子中,generateReqInner 访问了其作用域之外的封闭作用域里的参数 field, values, 从而变成了独立于commonGenerator 的闭包。

一个简单而经典的例子如下:

def outer():
def inner():
count = [1]
print 'inner: ', count[0]
count[0] += 1
return inner def outer2():
count = [1]
def inner2():
print 'inner2: ', count[0]
count[0] += 1
return inner2 def outer3(alist):
inners = []
for e in alist:
def inner3():
print 'inner3: ', e
inners.append(inner3)
return inners def outer4(alist):
inners = []
for e in alist:
def inner4(g):
def inner():
print 'inner4: ', g
return inner
inners.append(inner4(e))
return inners if __name__ == '__main__':
inner = outer()
inner()
inner()
inner2 = outer2()
inner2()
inner2() for outer in [outer3, outer4]:
inners = outer([1,2,3])
for inner in inners:
inner() ''' output
inner: 1
inner: 1
inner2: 1
inner2: 2
inner3: 3
inner3: 3
inner3: 3
inner4: 1
inner4: 2
inner4: 3
'''

在 inner 中,只访问了自己的局部变量;当调用 inner = outer() 后, inner 是一个普通函数,每次调用时 count 都会重新创建为 count = [1] ; 而在 inner2 中,访问了outer2 的变量 count, 这个变量在 inner2 的作用域外,成为了一个闭包。 当调用 inner2 = outer2() 后,inner2 存储了自由变量 count ,并在每次调用后都会增加 count[0],从而使得每次打印的值都不同。 注意,如果每次都这样调用 outer2()() ,其效果与 outer()() 是一样的,count 不会变化。因此,从某种意义来说,闭包更像是个动态的执行体,而不是静态的。

outer3 展示了使用闭包的一个注意事项,虽然也采用了闭包,但是闭包存的是循环结束后的最终值;如果要每个函数分别存储循环变量的每个值,就需要将循环变量作为封闭作用域的参数传给闭包。

应用####

闭包的一大应用是作为函数工厂,可以批量生产函数,模拟柯里化效果。柯里化的基本介绍可参阅博文:“函数柯里化(Currying)示例”。闭包 closure(x,y) = closure(x)(y) ,当传入不同的 y 时,就能生产不同的函数。比如幂次方求和函数 p(n,m) = 1^m + 2^m + ... + n^m ;p(n,1) 就是列表求和;p(n,2) 就是平方和; p(n,3) 就是立方和。 代码如下所示:

def p(alist,m):
return sum(map(lambda x: x**m, alist)) def pclosure(alist, m):
if m:
return lambda l: sum(map(lambda x: x**m, l))
if alist:
return lambda n: sum(map(lambda x: x**n, alist))
return lambda l,n: sum(map(lambda x: x**n, l)) def getlist(n):
return map(lambda x:x+1, range(n)) msum = pclosure([], 1)
print 'sum([1-3]^1) = ', msum(getlist(3))
print 'sum([1-5]^1) = ', msum(getlist(5)) msum = pclosure([], 2)
print 'sum([1-3]^2) = ', msum(getlist(3))
print 'sum([1-5]^2) = ', msum(getlist(5)) mpower = pclosure(getlist(10), None)
print 'sum([1-10]^1) = ', mpower(1)
print 'sum([1-10]^3) = ', mpower(3) plain = pclosure(None, None)
print 'sum([1-8]^1) = ', plain(getlist(8), 1)
print 'sum([1-8]^2) = ', plain(getlist(8), 2) ''' output
sum([1-3]^1) = 6
sum([1-5]^1) = 15
sum([1-3]^2) = 14
sum([1-5]^2) = 55
sum([1-10]^1) = 55
sum([1-10]^3) = 3025
sum([1-8]^1) = 36
sum([1-8]^2) = 204
'''

p 是一个普通的实现,每次都必须指定一个列表alist和一个数值m ;与之对应的是 pclosure 的实现。如果没有提供列表而提供了幂次 M,就返回一个函数,这个函数接受列表,求指定幂次的和 pclosure(alist, m) = pclosure(alist, M) , M 已指定; 如果提供了列表 LIST 而没有提供幂次 m ,就返回一个函数,这个函数接受一个幂次,对列表的指定幂次求和 pclosure(alist, m) = pclosure(LIST, m) , LIST 已指定;如果列表和幂次都没有提供,就退回到一个普通的二元函数p(alist,m) 分别指定不同的参数,就能生成不同种类的一类函数。是不是很有意思?

小结###

通过Python闭包结合配置自动生成函数,使得代码表达能力更强大了。结合函数式编程,其威力可拭目以待。

Python使用闭包结合配置自动生成函数的更多相关文章

  1. 在Python命令行和VIM中自动补全

    作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/ 1. VIM下的配置: wget https://github.com/rkulla/pydiction/arc ...

  2. python编写工具及配置(notepad++)

    学长跟我说老师实验室里用的ide是pycharm,我用了一天,整体还行,就是加载速度太慢,可是第二天用的时候就卡的想让人骂街,cpu占有率趋近100%,电脑配置不高,我寻思不能因为这个就马上换电脑吧, ...

  3. python的开发环境配置-Eclipse-PyDev插件安装

    安装PyDev插件的两种安装方法: 1.百度搜索PyDev 2.4.0.zip,下载后解压,得到Plugins和Feature文件夹,复制两文件夹到Eclipse目录,覆盖即可. 插件的版本要对应py ...

  4. python开发环境安装配置

    需要安装的软件: Python2.7.14和Python3.6.4   要在电脑上同时安装两个版本 开发工具:PyCharm 是一个jetbrains的python开发工具  idea系列之一 Pyt ...

  5. 巧用Salt,实现CMDB配置自动发现

    随着互联网+新形势的发展,越来越多的企业步入双态(稳敏双态)IT时代,信息化环境越来越复杂,既有IOE三层架构,也有VCE.Openstack等云虚拟化架构和互联网化的分布式大数据架构.所以,企业急需 ...

  6. sublime txet 3 python 开发环境安装配置

    下载python 下载地址:https://www.python.org/downloads/windows/ 下载sublime text 3 下载地址:https://www.sublimetex ...

  7. Python函数编程——闭包和装饰器

    Python函数编程--闭包和装饰器 一.闭包 关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数).而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量.参数.当其中一个 ...

  8. python的安装与配置

    pyhton的下载与安装 1.python官网地址:https://www.python.org 2.下载 Python 编辑器PyCharm PyCharm 是一款功能强大的 Python 编辑器 ...

  9. jenkins结合supervisor进行python程序发布后的自动重启

    jenkins结合supervisor进行python程序发布后的自动重启 项目背景: 通过jenkins发布kvaccount.chinasoft.com站点的python服务端程序,业务部门同事需 ...

随机推荐

  1. mysql 设置 innodb_print_all_deadlocks=ON, 保存死锁日志

    Introduced 5.6.2 Command-Line Format --innodb-print-all-deadlocks=# System Variable Name innodb_prin ...

  2. LeetCode-714.Best Time to Buy and Sell Stock with Transaction Fee

    Your are given an array of integers prices, for which the i-th element is the price of a given stock ...

  3. python3安装PIL提示Could not find a version that satisfies the requirement pil

    python3安装PIL提示如下错误,安装指令是pip3 install PIL,这个是因为PIL(Python Imaging Library)是Python中一个强大的图像处理库,但目前其只支持到 ...

  4. dedecms首页去掉index.html怎么设置

    很多网友用IIS服务器建站,反映说dedecms首页默认多了一个/index.html,一般是没有这个后缀的,直接就**.com,那么如何将dedecms首页去掉index.html呢?很简单,服务器 ...

  5. MySQL数据库查询操作进阶——多表查询

    多表查询 在大部分情况下,我们用到的表都是彼此相关联的,所以我们会有相当大的需求用到跨表的查询,这个时候我们就需要将相关联的表连起来做多表查询. 多表查询分为连表查询和子查询,连表查询即将相关联的表连 ...

  6. python基础入门--input标签、变量、数字类型、列表、字符串、字典、索引值、bool值、占位符格式输出

    # 在python3 中: # nian=input('>>:') #请输入什么类型的值,都成字符串类型# print(type(nian)) # a = 2**64# print(typ ...

  7. [js] Array.slice和类数组转数组

    a.call(b) 相当于把a方法放到b的原型上(实例私有方法)执行 Array.slice的用途 https://juejin.im/post/5b20b8596fb9a01e8d6a47c0 用法 ...

  8. Java 基础 面向对象之构造方法和关键字

    构造方法 构造方法简介 在开发中经常需要在创建对象的同时明确对象的属性值,比如员工入职公司就要明确他的姓名.年龄等属性信息. 那么,创建对象就要明确属性值,那怎么解决呢?也就是在创建对象的时候就要做的 ...

  9. matplot绘图

    import matplotlib.pyplot as pltimport numpy as npimport matplotlib # 设置matplotlib正常显示中文和负号matplotlib ...

  10. Linux du命令详解

    1.命令:du 2.命令功能:显示每个文件和目录的磁盘使用空间. 3.命令参数 -a或-all #显示目录中个别文件的大小. -b或-bytes #显示目录或文件大小时,以byte为单位. -c或-- ...