Python 如何与发电机形成多条管道?

Python 如何与发电机形成多条管道?,python,generator,Python,Generator,我正在使用python,并试图找到一种将多个生成器优雅地链接在一起的方法。例如,问题的一个例子是有一个根生成器,它提供某种类型的数据,每个值都被传递给它的“子对象”,就像级联一样,而级联又可能修改它们接收的对象。我可以走这条路线: for x in gen1: gen2(x) gen3(x) 但它丑陋而不优雅。我在考虑一种更实用的做事方式。管道可能更像这样: for x in gen3(gen2(gen1())): print x with open("access-

我正在使用python,并试图找到一种将多个生成器优雅地链接在一起的方法。例如,问题的一个例子是有一个根生成器,它提供某种类型的数据,每个值都被传递给它的“子对象”,就像级联一样,而级联又可能修改它们接收的对象。我可以走这条路线:

for x in gen1:
    gen2(x)
    gen3(x)

但它丑陋而不优雅。我在考虑一种更实用的做事方式。

管道可能更像这样:

for x in gen3(gen2(gen1())):
    print x
with open("access-log") as wwwlog:
    total = 0
    for line in wwwlog:
        bytes_as_str = line.rsplit(None,1)[1]
        if bytes_as_str != '-':
            total += int(bytes_as_str)
print("Total: {}".format(total))
例如:

for i, x in enumerate(range(10)):
    print i, x
在Python中没有一种分叉(或“t”)管道的方法。如果需要多个管道,则必须复制它们:
gen2(gen1())
gen3(gen1())
。目标是计算Apache web服务器日志中传输的数据字节数。假设采用以下日志格式:

81.107.39.38 -  ... "GET /ply/ HTTP/1.1" 200 7587
81.107.39.38 -  ... "GET /favicon.ico HTTP/1.1" 404 133
81.107.39.38 -  ... "GET /admin HTTP/1.1" 403 -
传统(非生成器)解决方案可能如下所示:

for x in gen3(gen2(gen1())):
    print x
with open("access-log") as wwwlog:
    total = 0
    for line in wwwlog:
        bytes_as_str = line.rsplit(None,1)[1]
        if bytes_as_str != '-':
            total += int(bytes_as_str)
print("Total: {}".format(total))
为此使用生成器表达式的生成器管道可以可视化为:

access-log => wwwlog => bytecolumn => bytes => sum() => total
可能看起来像:

with open("access-log") as wwwlog:
    bytecolumn = (line.rsplit(None,1)[1] for line in wwwlog)
    bytes = (int(x) for x in bytecolumn if x != '-')
print("Total: {}".format(sum(bytes)))
Dave Beazley的幻灯片和更多示例可用


如果不确切知道您要做什么,很难说更多,因此我们可以评估您正在做的每件事是否需要自定义生成器(生成器表达式/理解可以很好地用于许多事情,而无需声明生成器函数).

您可以将生成器转换为协程,这样它们就可以
发送()
并从彼此接收值(使用
(yield)
表达式)。这将使每个人都有机会更改他们接收到的值,和/或将它们传递给下一个生成器/协同程序(或完全忽略它们)

注意,在下面的示例代码中,我使用一个名为
coroutine
的装饰器来“初始化”生成器/coroutine函数。这导致它们在第一个
yield
expression/statement之前执行。这是一个稍有修改的版本,在youtube视频中显示了一个非常有启发性的演讲,题目是戴夫·比兹利在PyCon 2009上的演讲

正如您应该能够从生成的输出中看到的那样,数据值正在由每个管道进行处理,这些管道是通过一个
send()
发送到head Corroutine配置的,然后head Corroutine将其有效地“多路复用”到每个管道中。由于每个子协同程序也这样做,因此可以建立一个复杂的流程“树”

import sys

def coroutine(func):
    """ Decorator to "prime" generators used as coroutines. """
    def start(*args,**kwargs):
        cr = func(*args,**kwargs)  # Create coroutine generator function.
        next(cr)                   # Advance to just before its first yield.
        return cr
    return start

def pipe(name, value, divisor, coroutines):
    """ Utility function to send values to list of coroutines. """
    print('  {}: {} is divisible by {}'.format(name, value, divisor))
    for cr in coroutines:
        cr.send(value)

def this_func_name():
    """ Helper function that returns name of function calling it. """
    frame = sys._getframe(1)
    return frame.f_code.co_name


@coroutine
def gen1(*coroutines):
    while True:
        value = (yield)     # Receive values sent here via "send()".
        if value % 2 == 0:  # Only pipe even values.
            pipe(this_func_name(), value, 2, coroutines)

@coroutine
def gen2(*coroutines):
    while True:
        value = (yield)     # Receive values sent here via "send()".
        if value % 4 == 0:  # Only pipe values divisible by 4.
            pipe(this_func_name(), value, 4, coroutines)

@coroutine
def gen3(*coroutines):
    while True:
        value = (yield)     # Receive values sent here via "send()".
        if value % 6 == 0:  # Only pipe values divisible by 6.
            pipe(this_func_name(), value, 6, coroutines)

# Create and link together some coroutine pipelines.
g3 = gen3()
g2 = gen2()
g1 = gen1(g2, g3)

# Send values through both pipelines (g1 -> g2, and g1 -> g3) of coroutines.
for value in range(17):
    print('piping {}'.format(value))
    g1.send(value)
输出:

0
gen1:0可被2整除
gen2:0可被4整除
gen3:0可被6整除
管道1
管道2
gen1:2可以被2整除
管道3
管道4
gen1:4可以被2整除
gen2:4可以被4整除
管道5
管道6
gen1:6可以被2整除
gen3:6可被6整除
管道7
管道8
gen1:8可以被2整除
gen2:8可以被4整除
管道9
管道10
gen1:10可以被2整除
管道11
管道12
gen1:12可被2整除
gen2:12可被4整除
gen3:12可被6整除
管道13
管道14
gen1:14可以被2整除
管道15
管道16
gen1:16可被2整除
第二代:16可被4整除

下面是一个简单的例子:

def negate_nums(g):
    for x in g:
        yield -x

def square_nums(g):
    for x in g:
        yield x ** 2

def half_num(g):
    for x in g:
        yield x / 2.0

def compose_gens(first_gen,*rest_gens):
    newg = first_gen(compose_gens(*rest_gens)) if rest_gens else first_gen
    return newg

for x in compose_gens(negate_nums,square_nums,half_num,range(10)):
    print(x)

这里您正在合成生成器,以便在最后的
compose\u gens
调用中从右向左调用它们。您可以通过反转参数将其更改为管道。

我需要gen1为多个生成器提供数据。那么第1代(第2代(第3代,第4代),第5代)的顺序呢?我认为您必须创建多个管道:
gen1(第2代(第3代)(第5代))
gen1(第2代(第4代)(第5代))