Pythonic方法链接python生成器函数以形成管道
我正在使用python进行管道代码重构 假设我们有一系列的生成器函数,我们希望将这些函数链接起来,形成一个数据处理管道Pythonic方法链接python生成器函数以形成管道,python,generator,Python,Generator,我正在使用python进行管道代码重构 假设我们有一系列的生成器函数,我们希望将这些函数链接起来,形成一个数据处理管道 例如: #!/usr/bin/python import itertools def foo1(g): for i in g: yield i + 1 def foo2(g): for i in g: yield 10 + i def foo3(g): for i in g: yield 'foo3:
例如:
#!/usr/bin/python
import itertools
def foo1(g):
for i in g:
yield i + 1
def foo2(g):
for i in g:
yield 10 + i
def foo3(g):
for i in g:
yield 'foo3:' + str(i)
res = foo3(foo2(foo1(range(0, 5))))
for i in res:
print i
输出:
foo3:11
foo3:12
foo3:13
foo3:14
foo3:15
我不认为foo3(foo2(foo1(范围(0,5)))
是实现我的管道目标的pythonic方法。尤其是当管道中的级数较大时
我希望我能像jquery中的chain一样重写它。类似于:
range(0, 5).foo1().foo2().foo3()
或许
l = [range(0, 5), foo1, foo2, foo3]
res = runner.run(l)
但我对生成器这个话题还不熟悉,所以找不到一个方法来实现这一点
欢迎提供任何帮助。对于这种情况,我有时喜欢使用左折叠(Python中称为
reduce
):
from functools import reduce
def pipeline(*steps):
return reduce(lambda x, y: y(x), list(steps))
res = pipeline(range(0, 5), foo1, foo2, foo3)
或者更好:
def compose(*funcs):
return lambda x: reduce(lambda f, g: g(f), list(funcs), x)
p = compose(foo1, foo2, foo3)
res = p(range(0, 5))
继runner.run方法之后,让我们定义此实用程序函数:
def recur(ops):
return ops[0](recur(ops[1:])) if len(ops)>1 else ops[0]
例如:
>>> ops = foo3, foo2, foo1, range(0, 5)
>>> list( recur(ops) )
['foo3:11', 'foo3:12', 'foo3:13', 'foo3:14', 'foo3:15']
备选方案:反向排序
例如:
>>> list( backw([range(0, 5), foo1, foo2, foo3]) )
['foo3:11', 'foo3:12', 'foo3:13', 'foo3:14', 'foo3:15']
如果您的示例中的函数是一次性(或一次性)函数,下面是另一个答案。一些很好的变量命名和生成器表达式的使用对于小操作很有帮助
>>> g = range(0, 5)
>>> foo1 = (x+1 for x in g)
>>> foo2 = (x+10 for x in foo1)
>>> foo3 = ('foo3:' + str(x) for x in foo2)
>>> for x in foo3:
... print x
...
foo3:11
foo3:12
foo3:13
foo3:14
foo3:15
您可以使用以下命令组合当前生成器函数: 另一种选择是从Monad开始,使用fmap调用组合生成器-Java 8流用户熟悉这种语法:
def main():
odds = Just(["1", "22", "333", "4444", "55555"]) \
.fmap(lengths) \
.fmap(non_divisibles(2)) \
.fmap(list) \
.getValue()
print(odds) #prints [1, 3, 5]
def lengths(words: Iterable[Sized]) -> Iterable[int]:
return map(len, words)
@curry
def non_divisibles(div: int, numbers: Iterable[int]) -> Iterable[int]:
return (n for n in numbers if n % div)
注意,在这种情况下,函数不需要用@curry修饰。直到终端getValue()调用时,才会计算整个转换链
我不认为foo3(foo2(foo1(范围(0,5)))是实现我的管道目标的pythonic方法。尤其是当管道中的级数较大时
在我看来,有一种相当简单的链接生成器的方法:将每个生成器的结果分配给一个变量,每个变量都可以有一个描述性名称
range\u iter=range(0,5)
foo1\u iter=foo1(范围\u iter)
foo2_iter=foo2(foo1_iter)
foo3_iter=foo3(foo2_iter)
对于foo3_iter中的i:
印刷品(一)
我更喜欢使用高阶函数,例如areduce
或类似函数:
- 在我的实际例子中,通常每个foo*生成器函数都需要自己的其他参数,如果使用
,这是很棘手的reduce
- 在我的实际案例中,管道中的步骤在运行时不是动态的:对于我来说,拥有一个更适合动态案例的模式似乎有点奇怪/出乎意料
- 这与常规函数的编写方式有点不一致,其中每个函数都被显式调用,并且每个函数的结果都被传递给下一个函数的调用。是的,我想有点重复,但我很高兴“调用函数”被重复,因为(对我来说)它真的很清楚
- 无需导入:它使用核心语言功能
foo1(foo2(…)
这样的东西从长远来看更具可读性。但是,如果你有1000多个函数,它将不起作用!我认为将函数按相反的顺序排列是一种混乱。如果len(ops)return ops[-1](repur(ops[:-1]),那么return ops[-1](repur(ops[:-1])怎么办>1 else ops[0]
@rbierman非常好。是的,也可以。答案更新为反向订购代码。
def main():
odds = list * \
non_divisibles(2) * \
lengths * \
Just(["1", "22", "333", "4444", "55555"])
print(odds.getValue()) #prints [1, 3, 5]
@curry
def lengths(words: Iterable[Sized]) -> Iterable[int]:
return map(len, words)
@curry
def non_divisibles(div: int, numbers: Iterable[int]) -> Iterable[int]:
return (n for n in numbers if n % div)
def main():
odds = Just(["1", "22", "333", "4444", "55555"]) \
.fmap(lengths) \
.fmap(non_divisibles(2)) \
.fmap(list) \
.getValue()
print(odds) #prints [1, 3, 5]
def lengths(words: Iterable[Sized]) -> Iterable[int]:
return map(len, words)
@curry
def non_divisibles(div: int, numbers: Iterable[int]) -> Iterable[int]:
return (n for n in numbers if n % div)