背景###

在构建测试用例集时,常常需要编写一些函数,这些函数接受基本相同的参数,仅有一个参数有所差异,并且处理模式也非常相同。可以使用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. zabbix agentd安装

    一.Linux客户端1.创建zabbix用户 groupadd zabbix useradd -g zabbix -M -s /sbin/nologin zabbix 2.解压agent包 zabbi ...

  2. AMI:加密的机器映像。卷

    (一)定义:镜像AMI (Amazon Machine Image,机器映像)是一个可以将操作系统.用户的应用程序.配置等一起打包的加密机器映像.用于启用实例的预配置服务器模板.每个 AMI 由一个操 ...

  3. MySQL模拟Oralce闪回操作

    在前面的文章中我们介绍了MySQL误操作后数据恢复(update,delete忘加where条件),大概操作是通过sed命令把binlog中相关SQL误操作给逆向回来,然后导入SQL文件来恢复错误操作 ...

  4. OC照片选择器MJPhotoBrowser

    图片选择器,看cocoachina发现一个有趣的框架,很好用,分享一下,其实做出该功能我之前写过一篇博客,使用转场动画写的,就是图片的手势缩放没写,有兴趣可以看看 效果图: github地址:http ...

  5. 并发编程---Process对象的其他属性或方法

    Process对象的其他属性或方法 #join方法 from multiprocessing import Process import time,os ''' 需求:让主进程等着子进程运行完毕,才能 ...

  6. git命令操作的时候,出现中文名显示问题

    方法一:git config --global core.quotepath false 方法二: Windows系统的Git默认是不支持中文显示的,需要进行一系列的设置才能避免乱码的出现,下面总结如 ...

  7. vux icon

    官网:https://doc.vux.li/zh-CN/components/icon.html <icon type="success"></icon>& ...

  8. HBuilder 自动整理代码格式快捷键设置

    工具 ->选项

  9. 实现并发join的方法

    import threadingimport time def music(): print("begin to listen %s" %time.ctime()) time.sl ...

  10. k8s pv 的三种挂载模式

    ReadWriteOnce:可读可写,只能被一个Node节点挂载 ReadWriteMany:可读可写,可以被多个Node节点挂载 ReadOnlyMany:只读,能被多个Node节点挂载