What is the logic behind this particular composition of Python functions?

Consider the following Python snippet regarding function composition:

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:

  • Can someone explain me the compose operational logic? (How it works?)
  • Would it be possible (and how?) To get the same without using a shortcut for this?

I already looked here , here and here too , my problem does NOT understand what lambda or reduce means (I think I got, for example, that 2 in the usage example will be somewhat the first element in funcs to be composed). What I find harder to understand is the complexity of how the two lambda got merged / nested and mixed with *args, **kwargs here as the first reduce argument ...


EDIT:

First of all, @Martijn and @Borealid, thanks for your efforts and answers and for dedicating to me. (Sorry for the delay, I do this in my free time and don't always get much ...)

Ok, now come to the facts ...

About the 1st paragraph of my question:

First of all, I realized that I really did not get (but I hope I did it now) about these arguments *args, **kwargs variadic before that, at least **kwargs is optional (I good, good ?) This made me understand, for example, why mycompositefunc(2) works with this only one (without a keyword) argument passed.

Then I realized that the example would work even if replacing these *args, **args in the inner lambda with plain x . I assume that since in this example all 3 linked functions ( sin, cos, sqrt ) expect one (and only one) parameter ... and, of course, return one result ... so, more specifically, it works, because the first composite function expects only one parameter (the following, of course, will receive only one argument here, that is the result of the previous functions that are linked, so you CANNOT make functions that expect more than one argument after the first ... I know this a bit, but I I think you have what I'm trying to explain ...)

Now, having come to what remains a vague question for me:

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

How does this lambda magic work?

With all the respect that you deserve, and I bear you, it seems to me that both of you are mistaken to come to the conclusion that the end result is sqrt(sin(cos(*args, **kw))) . Actually this cannot be, the order of operation of the sqrt function changes clearly: it is not the last, but the first.

I say this because:

 >>> mycompositefunc(2) 0.1553124117201235 

its result is equal

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

then how do you get the error message

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

(which is due to an attempt to squareroot a negative float)

 #PS for completeness: >>> sqrt(cos(sin(2))) 0.7837731062727799 >>> cos(sin(sqrt(2))) 0.5505562169613818 

So, I understand that the composition of the functions will be made from the last to the first (that is, to compose (sin, cos, sqrt) => sin (cos (sqrt (x)))), but "why?" and how does this lambda magic work? still remains a little obscure to me ... Help / suggestions are greatly appreciated!

At the 2nd point (on transcribing essays without reduction)

@Martijn Pieters: your first lineup (“wrapped”) works and returns exactly the same result

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

Instead of the deployed version, unfortunately, loops to 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 to answer the second point, right?

+7
function python composition
source share
1 answer

The syntax *args, **kw in lambda and call syntax is the best way to pass arbitrary arguments. They take any number of positional and keyword arguments and simply pass them on to the next call. You can write the result of an external lambda as:

 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() as follows:

 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 same as calling reduce() ; call lambda for function chain.

So, the first two functions in the sequence are sin and cos , and they are replaced by:

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

Then it is passed to the next call as f , with sqrt g , so you get:

 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.

In the end, you created a function that calls sqrt() , the result of which is passed to cos() , and the result of this is passed to sin() . *args, **kw allows you to pass any number of arguments or keyword arguments, so the compose() function can be applied to all but called, provided that all but the first function take only one argument, of course.

+5
source share

All Articles