Python 在不丢失生成器状态的情况下合成包含分支逻辑的生成器函数

Python 在不丢失生成器状态的情况下合成包含分支逻辑的生成器函数,python,functional-programming,Python,Functional Programming,我有一个从模块化函数集合构建数据管道的过程 其中一个函数是一个开关,它允许根据数据内容执行不同的函数 在下面的代码中,这是执行测试(通过字典查找)并尝试将适当的函数插入生成器管道的switchero函数 def switcheroo(x, field, s_dict, default): for content in x: yield next(s_dict.get(content[field], default)([content])) 我可以成功地运行下面的代码,这

我有一个从模块化函数集合构建数据管道的过程

其中一个函数是一个开关,它允许根据数据内容执行不同的函数

在下面的代码中,这是执行测试(通过字典查找)并尝试将适当的函数插入生成器管道的
switchero
函数

def switcheroo(x, field, s_dict, default):
    for content in x:
        yield next(s_dict.get(content[field], default)([content]))
我可以成功地运行下面的代码,这样做会生成3个文件-但是,
test_a.txt
文件应该包含两个结果。相反,它只包含一个,因为
save
函数/generator正在丢失其占位符,并且每次调用它时都会从头开始重新计算-在这种情况下,会从头开始重新打开文件

如果我在没有
switcheroo
的情况下运行类似的管道,
save
生成器将保留其内部状态,并将多行保存到一个文件中

我已经尝试过编写
switcheroo
函数的其他方法,但我的限制是,我需要将它与我拥有的任何其他函数一起编写到一个
管道
生成器中,该生成器将在运行时迭代

其他限制是,我希望保持所有函数的模块化,这样它们就可以按任意顺序组合


以上生成3个文件供参考,其内容应为:

test_a.txt

test_b.txt

test_c.txt

但是,在test_a.txt中只有

2,Steve,a
由于
save
功能从一开始就被评估了两次。i、 e.每次通过管道将记录导入文件时,它都会从头开始重新创建文件。因此,“Tom”记录被保存,但当文件设置代码重新运行时,“Steve”记录将覆盖它

我想让它做的是,按照正常的发电机/产量模式,只进行一次初始设置


我可以将文件设置为“附加”,但我更感兴趣的是保留生成器模式,而不是序列化的细节-我希望最终得到一个工作模式,我可以自信地将其应用于以不同组合排列的任意模块化组件集合。就线性管道而言,我已经做到了这一点。但这种分支功能,即管道变成树状,是在给工程泼一把扳手

基于切普纳的评论:

def dup(src):  # to allow "peeking"
  for x in src: yield x; yield x
def switcheroo(x, field, s_dict, default):
  # Make generators that share x:
  x=dup(x)
  d={k:v(x) for k,v in s_dict.items()}
  for content in x:  # peek
    yield next(d.get(content[field], default))
请注意,
default
现在必须是一个生成器本身,并且从
su dict
生成的每个生成器必须在每次生成之前从其输入流中准确读取一次。可以使用
itertools.tee
而不是
dup
;虽然不那么麻烦,但它不会放松任何假设。

您的代码不起作用,因为您试图将迭代硬塞进更多的内容中。它需要一些推动

考虑一下,当switcheroo两次执行相同的save*函数时会发生什么。它调用这些函数两次(从而创建两个生成器,每个生成器打开文件进行写入,并覆盖前一个)。是的,您可以存储它们,但接下来您遇到了第二个问题—您已经传递了数据,p_save_a在该数据上迭代([content])。您无法扩展它,当您遇到使用简单列表对象的第二个p_save_调用时,您需要一个包装器

如果您想要一些有用的东西,请尝试以下方法:

def switcheroo(x, field, s_dict, default):
    iterators = {}
    class placeholder:
        def __init__(self):
            self.values = []
        def append(self, v):
            self.values.append(v)
        def __iter__(self):
            return self
        def __next__(self):
            if not self.values:
                raise StopIteration
            v = self.values.pop(0)
            return v
    for content in x:
        val = content[field]
        try:
            iter, iter_val = iterators[val]
        except Exception:
            iter_val = placeholder()
            iter = s_dict.get(val, default)(iter_val)
            iterators[val] = iter, iter_val
        iter_val.append(content)
        yield next(iter)

占位符类充当一个代理通信服务,一个向量,在遍历时可以对其进行迭代和扩展。当然,这在性能方面是糟糕的,但这只是概念的证明。

我认为
switch\u d
应该是有源发电机的
dict
,而不是发电机功能。这需要对
send
进行一些更改,因为它将通过生成器的
send
方法接收
content
的下一个值,而不是采用一个iterable参数。好主意-我会朝那个方向看-必须有一个既定的模式来执行类似的操作。另一个想法是
save
将打开的文件句柄作为参数,而不是文件名,或者以附加模式打开文件。这可能就足够了,而不必保留长寿命的生成器。这样可以解决眼前的问题,但这样做会限制出现在分支函数内(或之后)的任何函数必须避免使用状态感知生成器。这可能是我必须处理的一个约束,但如果有一种技术允许我在这个管道模式中利用生成器状态持久性,那将是非常有用的。我支持@chepner的回答,你需要保持活动生成器并使用
send
为它们提供下一个值,完成后,捕捉一个
StopIteration
。(更多信息发送:)美丽-这是一个梦想-非常感谢-解释也很有帮助-我在标记这一点和@Davis Herring的答案之间左右为难,这也起到了作用。再次感谢。在这种情况下,一个值就足够了,而不是一个列表,它的性能应该会更好一些。@DavisHerring是正确的。我使用列表作为一般示例,如果有保证的话,最多会有一个对象,使用单个值是最好的选择。为此,我非常喜欢那里的
dup
技巧,这是迄今为止我见过的最简单的“偷看”。让我想想“行为良好”假设可能不成立的情况,如果不是这样会发生什么。。。
6,Xavier,c
2,Steve,a
def dup(src):  # to allow "peeking"
  for x in src: yield x; yield x
def switcheroo(x, field, s_dict, default):
  # Make generators that share x:
  x=dup(x)
  d={k:v(x) for k,v in s_dict.items()}
  for content in x:  # peek
    yield next(d.get(content[field], default))
def switcheroo(x, field, s_dict, default):
    iterators = {}
    class placeholder:
        def __init__(self):
            self.values = []
        def append(self, v):
            self.values.append(v)
        def __iter__(self):
            return self
        def __next__(self):
            if not self.values:
                raise StopIteration
            v = self.values.pop(0)
            return v
    for content in x:
        val = content[field]
        try:
            iter, iter_val = iterators[val]
        except Exception:
            iter_val = placeholder()
            iter = s_dict.get(val, default)(iter_val)
            iterators[val] = iter, iter_val
        iter_val.append(content)
        yield next(iter)