问题
I was trying to understand how reduce
works through this website. The example they have mentioned is quite good and easy to understand.
http://book.pythontips.com/en/latest/map_filter.html#reduce
a = reduce((lambda x, y: x * y), [1, 2, 3, 4])
The above function will multiple each and every number in list and assign to a
.
However, I got totally stumped when I came across following function in project.
def compose(*fns):
return reduce(lambda acc, fn: lambda *args: acc(fn(*args)), fns, lambda _: _)
could someone help me breakdown this function to understand what it's suppose to do
回答1:
lambda
expressions can be difficult to follow, and especially so with this one, which returns a new function, also defined using lambda
.
Here is that same expression, but with some different line spacing:
def compose(*fns):
return reduce(lambda acc, fn: lambda *args: acc(fn(*args)),
fns,
lambda _: _)
Now I'll further expand this by exploding the lambda
s passed to reduce
as a regular def
statements:
def compose_2_fns(f, g):
# take 2 functions and return a new function that calls the first with the
# result of calling the second
def composed(*args):
return f(g(*args))
return composed
def _initial(x):
return x
def compose(*fns):
return reduce(compose_2_fns, fns, _initial)
Recall that reduce
works by giving it a method that takes 2 arguments, a sequence of objects (in this case, a sequence of functions), and an optional initial value.
reduce(reduce_fn, objs, first_obj)
If no initial value is given, reduce will take the first object in the sequence, as if you had called it like:
reduce(reduce_fn, objs[1:], objs[0])
Then the reduce function is called like this:
accumulator = first_obj
for obj in objs:
accumulator = reduce_fn(accumulator, obj)
return accumulator
So what your posted reduce
statement is doing is building up a big function by combining several smaller ones.
functions = (add_1, mult_5, add_3)
resulting_function -> lambda *args: add_1(mult_5(add_3(*args)))
So that:
resulting_function(2) -> (((2 + 3) * 5) + 1) -> 26
回答2:
Not very readable at first sight. Let's decompose:
First, def compose(*fns):
means that the compose function will receive an unknown number of arguments.
Next, let's decompose the reduce function:
reduce(
lambda acc, fn: lambda *args: acc(fn(*args)),
fns,
lambda _: _
)
As the doc indicates, reduce
takes 3 arguments:
def reduce(function, iterable, initializer=None):
So, in your case: the function
is lambda acc, fn: lambda *args: acc(fn(*args))
, fns
is the iterable
, and it will be initialize with lambda _: _
The initializer
indicates that the arguments of compose
will be functions. lambda _: _
is a "neutral element" for function (the same as "0" for addition or "1" for multiplication). I guess it's there essentially when fns is empty.
Now for the main part:
lambda acc, fn: lambda *args: acc(fn(*args))
this is a function that take two functions acc
and fn
and returns the lambda function lambda *args: acc(fn(*args))
.
Let's take an example:
>>> reduce((lambda acc, fn: acc ** fn), [1, 2, 3, 4])
1
>>> reduce((lambda acc, fn: fn ** acc ), [1, 2, 3, 4])
262144
Here acc and fn are not function, but integers. acc
is the "accumulated/reduced" so far, and fn
is the "next" step.
With functions, it will be the same acc
will be the "called functions" so far, and fn the next function.
So lambda acc, fn: lambda *args: acc(fn(*args))
will return a (lambda) function, that will return acc(fn(the_arguments))
.
reduce(lambda acc, fn: lambda *args: acc(fn(*args)), fns, lambda _: _)
will then returns a function consisting of applying each functions of fns
to its args, with identity by default (lambda _: _
).
Let's take an example:
>>> def square(x):
... return x**2
...
>>> def increment(x):
... return x+1
...
>>> def half(x):
... return x/2
...
>>> compose(square, increment, half)
<function <lambda> at 0x7f5321e13de8>
>>> g=compose(square, increment, half)
>>> g(5)
9
So, g(x) = square(increment(half(x)))
With Kasramvd's example:
compose(max, min)([[2, 4], [3, 5]])
is the same as:
max(min([[2, 4], [3, 5]]))
min([[2, 4], [3, 5]])
will returns [2,4]
and max([2,4])
is 4. Thus compose(max, min)([[2, 4], [3, 5]])=4
回答3:
That reduce
is just a function composer. It accepts an iterable of functions and combines them.
One important point about the reduce
is that when you pass 2 arguments to your function (in this case acc
, fn
) based on how you are using them, at first iteration python uses the last two arguments in your iterable instead of them, and in next iterations it uses the result of latest calculation instead of second argument passed to the lamda
or any function passed as the constructor to the reduce
(in this case fn
). Now For a better demonstration here are all the arguments passed to the reduce
in distinction:
the function: lambda acc, fn: lambda *args: acc(fn(*args))
- the use of second function is being able to pass the arguments to the inner function at each iteration.
iterable argument: fns
initial argument: lambda _: _
If initial is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty.
As you can see in passed function it calls the inner functions with arguments and passes it to the next function like fog
in mathematics which is f(g(x))
. But since you can pass an undefined number of arguments to reduce this function can compose multiple function.
Here is an example:
In [10]: compose(max, min)([[2, 4], [3, 5]])
Out[10]: 4
Also note that this is not a pythonic and recommended way of composing the functions at all. Because first of all it's not readable and easily understandable secondly it's using a lot of extra function calls which is not optimum for such task.
来源:https://stackoverflow.com/questions/45503434/how-lambda-works-with-reduce