Reputation: 19
Is there a better way to iteratively apply a list of functions to a dictionary? Here is an example of what I want to do. But this uses recursion.
def func1(h: dict):
h['foo']=10
return(h)
def func2(h: dict):
h['bar']=100
return(h)
def func3(h: dict):
h['baz']=h['foo']+h['bar']
return(h)
func3(func2(func1({'firstElement':'good'})))
Produces expected output:
{'bar': 100, 'baz': 110, 'firstElement': 'good', 'foo': 10}
I want to provide the functions as a array and produce the same output. Here is what I have tried and works:
def recApply(flist, h=None):
"""
Helper Apply the list of functions iteratively over the dictionary passed
:obj: function list each will be applied to the dictionary sequentially.
"""
#if no dictionary passed, then set the dictionary.
if(h == None):
h = {}
#iteratively call functions with dictionary as a passed parameter and returning a derived dictionary
for f in flist:
h = f(h)
return(h)
flist = [func1,func2,func3]
recApply(flist,{'firstElement':'good'})
This produces the desired output:
{'bar': 100, 'baz': 110, 'firstElement': 'good', 'foo': 10}
Is there a way to do this that is more readable, removes the recApply function and hopefully minimizes dictionary copies?
Upvotes: 0
Views: 105
Reputation: 82929
You could also use reduce
(or functools.reduce
in Python 3) with the starting dictionary as the initializer
parameter:
>>> from functools import reduce # Python 3
>>> reduce(lambda (x, f): f(x), (func1, func2, func3), {'firstElement':'good'})
{'bar': 100, 'baz': 110, 'firstElement': 'good', 'foo': 10}
This will apply the functions one after the other to the initializer or the result of the previous function.
You could also combine this with functools.partial
to create a chained function that can then be applied to different input dictionaries:
>>> from functools import partial
>>> chain = partial(reduce, lambda (x, f): f(x), (func1, func2, func3))
>>> chain({'firstElement':'good'})
{'bar': 100, 'baz': 110, 'firstElement': 'good', 'foo': 10}
And you could generalize that further, making it a partial
of a partial
function...
>>> chainer = partial(partial, reduce, lambda (x, f): f(x))
>>> chain = chainer((func1, func2, func3))
>>> chain({'firstElement':'good'})
{'bar': 100, 'baz': 110, 'firstElement': 'good', 'foo': 10}
Upvotes: 1
Reputation: 531708
reduce
(or functools.reduce
in Python 3) can be used to compose a list of functions into a single function. This requires you to define a composition function:
def compose(f, g):
def _(x):
return f(g(x))
return _
and an identity function:
def identity(x):
return x
Using these, you can create a function g
that applies each function in order to an initial input.
g = reduce(compose, [func3, func2, func1], identity)
assert (g({'firstElement': 'good'}) ==
{'firstElement': 'good', 'foo': 10, 'bar': 100, 'baz': 110})
Note this works because func1
, func2
and func3
are similar enough to pure functions that you can make use of the function monoid. Loosely speaking, it just means that function composition is associative (compose(f, compose(g, h))
is the same as compose(compose(f, g), h)
) and that the identity function is neutral under composition (compose(identity, f)
and compose(f, identity)
are both the same as f
itself).
Your three functions aren't really pure functions; they are more like identity functions with side effects. However, you can treat them as pure functions because you are using them as if they were defined as, for example,
def func1(h):
h1 = {}
h1.update(h)
h1['foo'] = 10
return h1
Exercise for the reader: determine if my call to reduce
actually defines g(x) = func3(func2(func1(x)))
or g(x) = func1(func2(func3(x))
.
Upvotes: 2
Reputation: 25799
You don't need to return your dictionaries and re-assign references - mutable types are passed by reference so:
def func1(h):
h['foo'] = 10
def func2(h):
h['bar'] = 100
def func3(h):
h['baz'] = h['foo'] + h['bar']
start_dict = {'firstElement': 'good'}
for f in (func1, func2, func3):
f(start_dict)
print(start_dict)
# {'firstElement': 'good', 'baz': 110, 'bar': 100, 'foo': 10}
would to perfectly fine.
Upvotes: 2