这个特定的Python函数组合背后的逻辑是什么?

dan*_*tra 7 python function composition

请考虑以下有关函数组合的Python代码段:

from functools import reduce
def compose(*funcs):
    # compose a group of functions into a single composite (f(g(h(..(x)..)))
    return reduce(lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)), funcs)


### --- usage example:
from math import sin, cos, sqrt
mycompositefunc = compose(sin,cos,sqrt)
mycompositefunc(2)
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

  1. 有人可以解释一下compose"操作逻辑"吗?(这个怎么运作?)
  2. 是否可以(以及如何?)在不使用reduce情况下获得相同的东西?

我已经看过这里,这里这里,我的问题是明白是什么lambda意思或reduce做(例如,我认为我2在使用示例中将有点是funcs要编写的第一个元素).我发现更难理解的是两者如何lambda组合/嵌套并在*args, **kwargs这里作为reduce第一个参数混合的复杂性......


编辑:

首先,@ Martijn和@Borealid,感谢您的努力和答案以及您献给我的时间.(抱歉延误,我在业余时间这样做,并不总是有很多......)

好的,现在来看看事实......

关于我的问题的第1点:

在此之前,我意识到我之前没有真正得到的(但我希望我现在做的)关于那些*args, **kwargs可变参数之前是至少 **kwargs 不是强制性的(我说得好,对吧?)这让我理解,例如,为什么mycompositefunc(2)有效只有一个(非关键字)传递参数.

然后,我意识到这个例子甚至可以用*args, **args简单的方法替换内部lambda中的那个x.我想这是因为,在这个例子中,所有3个组合函数(sin, cos, sqrt)都期望一个(和一个唯一的)参数...当然,返回一个结果......所以,更具体地说,它起作用,因为第一个组合函数期待只有一个参数(以下其他参数在这里自然只会得到一个参数,这是以前组合函数的结果,所以你不能编写在第一个参数之后需要多个参数的函数...我知道它有点扭曲,但我认为你有我想解释的......)

现在,我在这里找到真正不明确的问题:

lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs))
Run Code Online (Sandbox Code Playgroud)

那个lambda嵌套"魔法"是如何工作的?

有了你应得的所有尊重并且我忍受了你,在我看来,你们两个都错了,最后的结果应该是:sqrt(sin(cos(*args, **kw))).它实际上不可能,sqrt函数的设备顺序明显颠倒过来:它不是最后编写的,而是第一个.

我这样说是因为:

>>> mycompositefunc(2)
0.1553124117201235
Run Code Online (Sandbox Code Playgroud)

它的结果等于

>>> sin(cos(sqrt(2)))
0.1553124117201235
Run Code Online (Sandbox Code Playgroud)

而你得到一个错误

>>> sqrt(sin(cos(2)))
[...]
ValueError: math domain error
Run Code Online (Sandbox Code Playgroud)

(这是因为试图将负浮点数设为平方)

#P.S. for completeness:

>>> sqrt(cos(sin(2)))
0.7837731062727799

>>> cos(sin(sqrt(2)))
0.5505562169613818
Run Code Online (Sandbox Code Playgroud)

所以,我明白函数组成将从最后一个到第一个(即:compose(sin,cos,sqrt)=> sin(cos(sqrt(x))))但是" 为什么? "以及如何lambda嵌套"魔法"有效吗?仍然有点不清楚...帮助/建议非常感谢!

在第2点(关于重写撰写没有减少)

@Martijn Pieters:你的第一个作曲("包裹"的作品)起作用并返回完全相同的结果

>>> mp_compfunc = mp_compose(sin,cos,sqrt)
>>> mp_compfunc(2)
0.1553124117201235
Run Code Online (Sandbox Code Playgroud)

相反,未打开的版本循环直到RuntimeError: maximum recursion depth exceeded......

@Borealid:你的foo/bar示例不会有两个以上的函数用于合成,但我认为这只是为了解释不是为了回答第二点,对吧?

Mar*_*ers 5

*args, **kw在这两个语法lambda签名和调用语法都转嫁的最佳方式任意参数.它们接受任意数量的位置和关键字参数,并将这些参数传递给下一个调用.您可以将外部结果写lambda为:

def _anonymous_function_(*args, **kw):
    result_of_g = g(*args, **kw)
    return f(result_of_g)
return _anonymous_function
Run Code Online (Sandbox Code Playgroud)

这个compose函数可以不用reduce()这样重写:

def compose(*funcs):
    wrap = lambda f, g: lambda *args, **kw: f(g(*args, **kw))
    result = funcs[0]
    for func in funcs[1:]:
        result = wrap(result, func)
    return result
Run Code Online (Sandbox Code Playgroud)

这与reduce()呼叫完全相同; 为函数链调用lambda.

因此,序列中的前两个函数是sincos,并且这些函数被替换为:

lambda *args, **kw: sin(cos(*args, **kw))
Run Code Online (Sandbox Code Playgroud)

这则传递给下一个调用的f,与sqrt g等你拿:

lambda *args, **kw: (lambda *args, **kw: sin(cos(*args, **kw)))(sqrt(*args, **kw)))
Run Code Online (Sandbox Code Playgroud)

可以简化为:

lambda *args, **kw: sin(cos(sqrt(*args, **kw)))
Run Code Online (Sandbox Code Playgroud)

因为f()是一个将其参数传递给嵌套sin(cos())调用的lambda .

最后,您已经生成了一个调用函数sqrt(),其结果将传递给它cos(),然后将其输出传递给sin().将*args, **kw让您在任何数量的参数或关键字参数传递,所以compose()功能可以应用到任何东西比是可调用,只要所有,但第一个函数只接受一个参数,当然.