Python使用閉包結合配置自動生成函數

背景

在構建測試用例集時,經常須要編寫一些函數,這些函數接受基本相同的參數,僅有一個參數有所差別,而且處理模式也很是相同。可使用Python閉包來定義模板函數,而後經過參數調節來自動化生產不一樣的函數。html

示例

看以下代碼: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[字段名,值列表], 就能自動生成這些函數 ?express

使用閉包能夠達到這個目標。見以下代碼所示:編程

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函數。妙不妙,生產函數的函數 !json

閉包

理解

按維基的解釋: 閉包是引用了自由變量的函數。在例子中,閉包就是 generateReqInner , 引用了傳入的自由變量 field, values, 從而在 commonGenerator 調用結束以後,generateReqInner 依然存在可以被訪問,且功能效果等同於 generateReqInner(startTime, endTime, field, values) 。閉包

知乎上有句話頗有啓發性: 閉包就是一種特殊性質的數據,只不過這種數據剛好是攜帶了數據的代碼塊,是一個潛伏起來的隨時待執行的完整的對象體(數據-行爲綁定的執行體)。從這個角度來講,也能夠理解閉包的實現:app

  • 要件一: 閉包一定存在於一個封閉的做用域 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閉包結合配置自動生成函數,使得代碼表達能力更強大了。結合函數式編程,其威力可拭目以待。

相關文章
相關標籤/搜索