2016-01-25 64 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()))