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

时间:2015-08-08 12:23:53

标签: 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)

我有两个问题:

  1. 有人可以解释一下compose“操作逻辑”吗? (它是如何工作的?)
  2. 是否有可能(以及如何?)为此获得同样的事情without using reduce
  3. 我已查看hereherehere too,我的问题是 NOT 了解lambda的含义或reduce的含义(例如,我认为我在使用示例中2将成为funcs中要编写的第一个元素。 我发现难以理解的是两个lambda如何组合/嵌套并与*args, **kwargs混合在一起作为reduce第一个参数的复杂性......

    修改

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

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

    关于我的问题的第1点:

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

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

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

    lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs))
    

    lambda嵌套“魔法”是如何工作的?

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

    我这样说是因为:

    >>> mycompositefunc(2)
    0.1553124117201235
    

    其结果等于

    >>> sin(cos(sqrt(2)))
    0.1553124117201235
    

    但是你的错误

    >>> sqrt(sin(cos(2)))
    [...]
    ValueError: math domain error
    

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

    #P.S. for completeness:
    
    >>> sqrt(cos(sin(2)))
    0.7837731062727799
    
    >>> cos(sin(sqrt(2)))
    0.5505562169613818
    

    所以,我明白函数组成将从最后一个到第一个(即:compose(sin,cos,sqrt)=&gt; sin(cos(sqrt(x))))但是“< em>为什么?“和 lambda嵌套”魔法“是如何工作的?对我来说仍然有点不清楚...帮助/建议非常感谢!

    关于第二点(关于重写撰写而不减少)

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

    >>> mp_compfunc = mp_compose(sin,cos,sqrt)
    >>> mp_compfunc(2)
    0.1553124117201235
    

    不幸的是,展开的版本循环到RuntimeError: maximum recursion depth exceeded ...

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

1 个答案:

答案 0 :(得分:5)

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

def _anonymous_function_(*args, **kw):
    result_of_g = g(*args, **kw)
    return f(result_of_g)
return _anonymous_function

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

这与reduce()电话完全相同;将lambda称为函数链。

因此,序列中的前两个函数是sincos,它们被替换为:

lambda *args, **kw: sin(cos(*args, **kw))

然后通过f sqrt将其转移到下一个调用g,以便获得:

lambda *args, **kw: (lambda *args, **kw: sin(cos(*args, **kw)))(sqrt(*args, **kw)))

可以简化为:

lambda *args, **kw: sin(cos(sqrt(*args, **kw)))

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

最后,您创建了一个调用sqrt()的函数,其结果传递给cos(),然后将其输出传递给sin()*args, **kw允许您传入任意数量的参数或关键字参数,因此compose()函数可以应用于任何而不是可调用,前提是除了第一个函数之外的所有函数都需要当然只是一个论点。