Python 生成器 "chain" 在 for 循环中

Python Generator "chain" in a for loop

我正在尝试为我从数据源读取的数据设置一个 "processing pipeline",并在读取的每个项目上应用一系列运算符(使用生成器)。

一些演示相同问题的示例代码。

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 似乎不是每次通过 for 循环保存 op 的值,所以它每次都应用第三个函数。 有没有办法每次通过for循环"bind"将实际的运算符函数传给生成器表达式?

我可以通过将 for 循环中的生成器表达式更改为列表理解来轻松解决这个问题,但由于实际数据要大得多,我不想在任何时候将其全部存储在内存中.

这可能是您想要的 - 创建复合函数:

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()))

您可以定义一个小助手来组合函数但顺序相反:

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 表达式。此顺序不常见,但可确保应用您的函数 left-to-right(这可能是您所期望的):

使用这个,你的 main 就变成

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

这个问题的原因是您正在创建一个深度嵌套的生成器生成器并在 循环之后评估整个事情,当 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()))

您可以通过在新函数中创建生成器来强制绑定变量。例如

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_operatormap 内置函数(在 python 3.x 中)几乎相同。因此,只需使用它即可。