2016-01-25 65 views
8

我想爲我正在從數據源讀取的數據設置一個「處理管道」,並將一系列操作符(使用生成器)應用於每個項目因爲它被讀取。for循環中的Python生成器「鏈」

一些演示相同問題的示例代碼。

def reader(): 
    yield 1 
    yield 2 
    yield 3 

def add_1(val): 
    return val + 1 

def add_5(val): 
    return val + 5 

def add_10(val): 
    return val + 10 

operators = [add_1, add_5, add_10] 

def main(): 
    vals = reader() 

    for op in operators: 
     vals = (op(val) for val in vals) 

    return vals 

print(list(main())) 

期望[17, 18, 19]
實際[31, 32, 33]

的Python似乎無法通過對每次循環節約op價值,所以不是每次都適用的第三個功能。 有沒有一種方法可以在每次通過for循環時將實際的運算符函數「綁定」到生成器表達式?

我可以通過將for循環中的生成器表達式更改爲列表理解來解決這個問題,但由於實際數據要大得多,所以我不想將它全部存儲在內存中的任何一點。

+0

謝謝大家! 'map'解決方案對我來說效果最好,因爲我還想在for循環中做其他事情(涉及日誌記錄,附加檢查等)。在我真正的程序中,每個'operator'實際上是一個帶有__call__'的類,並且還有一些我需要處理的其他函數和屬性。 'reduce'解決方案也可以很好地工作,但是如果不將函數中的每個操作符都包裝在一起執行這些額外的操作,就會失去這種能力。 – gtback

回答

2

您可以通過在新函數中創建生成器來強制綁定一個變量。例如。

def map_operator(operator, iterable): 
    # closure value of operator is now separate for each generator created 
    return (operator(item) for item in iterable) 

def main(): 
    vals = reader() 
    for op in operators: 
     vals = map_operator(op, vals) 
    return vals 

然而,map_operator是幾乎等同於map內建(在python 3.X)。所以只需使用它。

+0

哇,我真的很想知道爲什麼我沒有想到使用'vals = map(op,vals)'.. 。 –

+1

在Python 2中,請確保使用['itertools.imap'](https://docs.python.org/2.7/library/itertools.html#itertools.imap)。我經過慘痛的教訓才學到這個。 – gtback

1

這可能是你想要的 - 創建一個複合功能:

import functools 

def compose(functions): 
    return functools.reduce(lambda f, g: lambda x: g(f(x)), functions, lambda x: x) 

def reader(): 
    yield 1 
    yield 2 
    yield 3 

def add_1(val): 
    return val + 1 

def add_5(val): 
    return val + 5 

def add_10(val): 
    return val + 10 

operators = [add_1, add_5, add_10] 

def main(): 
    vals = map(compose(operators), reader()) 
    return vals 

print(list(main())) 
2

可以定義一個小助手用來構成功能但以相反的順序

import functools 

def compose(*fns): 
    return functools.reduce(lambda f, g: lambda x: g(f(x)), fns) 

即您可以使用compose(f,g,h)生成等效於lambda x: h(g(f(x)))的lambda表達式。這個順序是罕見的,但可以確保你的函數應用於左到右(這可能是你所期望的):

利用這一點,你main就像變成

def main(): 
    vals = reader() 
    f = compose(add_1, add_5, add_10) 
    return (f(v) for v in vals) 
+0

訂單上的好處,我沒有想到。我已經解決了我的答案。 – texasflood

+0

請注意,這改變了操作的順序:即,OP的代碼首先爲所有值計算op_1,然後計算op_5等,然後將所有操作應用於val_1,然後應用於val_2,等等。取決於應用,這可能是完全可以的或者是一個問題。 (只是想指出) –

+0

https://mathieularose.com/function-composition-in-python/ – Alex

1

原因這個問題是你正在創建一個深度嵌套的生成器,並且在之後評估整個事件,當op已被綁定到列表中的最後一個元素時 - 類似於相當常見的"lambda in a loop"問題。

從某種意義上說,你的代碼是大致相同的:

for op in operators: 
    pass 

print(list((op(val) for val in (op(val) for val in (op(val) for val in (x for x in [1, 2, 3]))))) 

一個(不是很漂亮)的方式來解決這一問題將是zip值另一個發電機,重複同樣的動作:

def add(n): 
    def add_n(val): 
     return val + n 
    return add_n 
operators = [add(n) for n in [1, 5, 10]] 

import itertools 
def main(): 
    vals = (x for x in [1, 2, 3]) 

    for op in operators: 
     vals = (op(val) for (val, op) in zip(vals, itertools.repeat(op))) 

    return vals 

print(list(main()))