简体   繁体   English

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

[英]What's the logic behind this particular Python functions composition?

Consider the following Python snippet concerning functions 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)

I have two questions: 我有两个问题:

  1. Can someone please explain me the compose "operational logic"? 有人可以向我解释compose “操作逻辑”吗? (How it works?) (这个怎么运作?)
  2. Would it be possible (and how?) to obtain the same thing without using reduce for this? 是否可以(以及如何?)在不使用reduce情况下获得相同的东西?

I already looked here , here and here too , my problem is NOT understanding what lambda means or reduce does (I think I got, for instance, that 2 in the usage example will be somewhat the first element in funcs to be composed). 我已经看到这里这里这里过 ,我的问题是没有理解什么lambda手段或reduce不会(我觉得我得到的,例如,这2在使用例如将有所第一要素funcs被组成)。 What I find harder to understand is rather the complexity of how the two lambda s got combined/nested and mixed with *args, **kwargs here as reduce first argument ... 我发现难以理解的是两个lambda如何组合/嵌套并与*args, **kwargs混合的复杂性*args, **kwargs在这里作为reduce第一个参数...


EDIT: 编辑:

First of all, @Martijn and @Borealid, thank you for your effort and answers and for the time you are dedicating to me. 首先,@ Martijn和@Borealid,感谢您的努力和答案以及您献给我的时间。 (Sorry for the delay, I do this in my spare time and not always have aa lot...) (抱歉延误,我在业余时间这样做,并不总是有很多......)

Ok, coming to facts now... 好的,现在来看看事实......

About 1st point on my question: 关于我的问题的第1点:

Before anything, I realized what I didn't really got (but I hope I did now) about those *args, **kwargs variadic arguments before is that at least **kwargs is not mandatory (I say well, right?) This made me understand, for instance, why mycompositefunc(2) works with that only one (non keyword) passed argument. 在此之前,我意识到我没有真正得到的(但我希望我现在做的)关于那些*args, **kwargs变量参数之前是至少 **kwargs 不是强制性的 (我说得好,对吧?)例如,让我理解为什么mycompositefunc(2)只使用一个(非关键字)传递参数。

I realized, then, that the example would work even replacing those *args, **args in the inner lambda with a simple x . 然后,我意识到这个例子甚至可以用简单的x替换内部lambda中的*args, **args I imagine that's because, in the example, all 3 composed functions ( sin, cos, sqrt ) expect one (and one only) parameter... and, of course, return a single result... so, more specifically, it works because the first composed function expect just one parameter (the following others will naturally get only one argument here, that's the result of the previous composed functions, so you COULDN'T compose functions that expect more than one argument after the first one... I know it's a bit contort but I think you got what I'm trying to explain...) 我想这是因为,在这个例子中,所有3个组合函数( sin, cos, sqrt )都期望一个(和一个唯一的)参数...当然,返回一个结果......所以,更具体地说,它的工作原理因为第一个组合函数只需要一个参数(以下其他函数在这里自然只会得到一个参数,这是以前组合函数的结果,所以你不能组成在第一个参数之后需要多个参数的函数...我知道这有点扭曲,但我认为你得到了我想解释的......)

Now coming to what remains the real unclear matter for me here: 现在,我在这里找到真正不明确的问题:

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

How does that lambda nesting "magic" works? 那个lambda嵌套“魔法”是如何工作的?

With all the great respect you deserve and I bear you, it seems to me like both of you are wrong coming to the conclusion the final result shall be: sqrt(sin(cos(*args, **kw))) . 有了你应得的所有尊重并且我忍受了你,在我看来你们两个都错了得出结论,最终结果应该是: sqrt(sin(cos(*args, **kw))) It actually can't be, the order of appliance of the sqrt function is clearly reversed: it's not the last to be composed but the first. 它实际上不可能,sqrt函数的设备顺序明显颠倒过来:它不是最后编写的,而是第一个。

I say this because: 我这样说是因为:

>>> mycompositefunc(2)
0.1553124117201235

its result is equal to 它的结果等于

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

whereas you get an error with 而你得到一个错误

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

(that's due to trying to squareroot a negative float) (这是因为试图将负浮点数设为平方)

#P.S. for completeness:

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

>>> cos(sin(sqrt(2)))
0.5505562169613818

So, I understand that the functions composition will be made from the last one to the first ( ie : compose(sin,cos,sqrt) => sin(cos(sqrt(x))) ) but the " why? " and how does that lambda nesting "magic" works? 所以,我明白函数组成将从最后一个到第一个(即:compose(sin,cos,sqrt)=> sin(cos(sqrt(x))))但是“ 为什么? ”以及如何lambda嵌套“魔法”有效吗? still remains a bit unclear for me... Help/Suggestions very appreciated! 仍然有点不清楚...帮助/建议非常感谢!

On 2nd point (about rewriting compose without reduce) 在第2点(关于重写撰写没有减少)

@Martijn Pieters: your first compose (the "wrapped" one) works and returns exactly the same result @Martijn Pieters:你的第一个作曲(“包裹”的作品)起作用并返回完全相同的结果

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

The unwrapped version, instead, unfortunately loops until RuntimeError: maximum recursion depth exceeded ... 不幸的是,展开的版本循环直到RuntimeError: maximum recursion depth exceeded ...

@Borealid: your foo/bar example will not get more than two functions for composition but I think it was just for explanations not intended for answering to second point, right? @Borealid:你的foo / bar示例不会有两个以上的函数用于合成,但我认为这只是为了解释不是为了回答第二点,对吧?

The *args, **kw syntax in both the lambda signature and call syntax are the best way to pass on arbitrary arguments . lambda签名和调用语法中的*args, **kw语法是传递任意参数的最佳方法。 They accept any number of positional and keyword arguments and just pass those on to a next call. 它们接受任意数量的位置和关键字参数,并将这些参数传递给下一个调用。 You could write the result of the outer lambda as: 您可以将外部lambda的结果写为:

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

The compose function can be rewritten without reduce() like this: 可以在没有reduce()情况下重写compose函数,如下所示:

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

This does the exact same thing as the reduce() call; 这与reduce()调用完全相同; call the lambda for the chain of functions. 为函数链调用lambda。

So, the first two functions in the sequence are sin and cos , and these are replaced by: 因此,序列中的前两个函数是sincos ,它们被替换为:

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

This then is passed to the next call as f , with sqrt g , so you get: 然后将其作为f传递给下一个调用,使用sqrt g ,所以你得到:

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

which can be simplified to: 可以简化为:

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

because f() is a lambda that passes its arguments to the nested sin(cos()) call. 因为f()是一个lambda,它将其参数传递给嵌套的sin(cos())调用。

In the end, then, you have produced a function that calls sqrt() , the result of which is passed to cos() , and the output of that is then passed to sin() . 最后,您生成了一个调用sqrt()的函数,其结果传递给cos() ,然后将其输出传递给sin() The *args, **kw lets you pass in any number of arguments or keyword arguments, so the compose() function can be applied to anything than is callable, provided that all but the first function takes just one argument, of course. *args, **kw允许您传入任意数量的参数或关键字参数,因此compose()函数可以应用于除可调用之外的任何内容 ,前提是除了第一个函数之外的所有函数只需要一个参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM