Reputation: 2272
Is there any clean way to apply a list of functions on an object in Python without lambda or list comprehensions? Like the Haskell expression:
map ($ obj) [foo1,foo2]
Example with lambda in Python:
response = map(lambda foo:foo(obj),[foo1,foo2]) #fooX:object->Bool
Is it extendable to class functions?
Perhaps something from operator or itertools?
Upvotes: 20
Views: 18271
Reputation: 3940
I think the question can be read in two ways, apply a list of functions to an object and yield the result of each in a list (Single input, multiple output) OR apply a list of function to an object in sequence (Singe input, single output).
The latter is solved by the compose function, or by defining your own:
def compz(t, funcs):
return t if not funcs else compz(funcs[0](t), funcs[1:])
print(compz("str", [lambda x: x+"A", lambda x: x+"B", lambda x: x+"C"]))
print(compz("Hello, World", [lambda x: f"<h1>{x}</h1>", lambda x: f"<body>{x}</body>", lambda x: f"<html>{x}</html>",]))
print(compz(2, [lambda x: x+2, lambda x: x+5, lambda x: x+7]))
print(compz(2, [lambda x: x**2, lambda x: x**2, lambda x: x**2]))
print(compz(2, [lambda x: x**2, lambda x: x**2]))
print(compz(2, [lambda x: x**2]))
print(compz(2, []))
strABC
<html><body><h1>Hello, World</h1></body></html>
16
256
16
4
2
Most answers to the latter are equivalent, but really the list comprehension either because the functions are applied in "parallel" not in sequence or because you have to import a separate library. looking at a few answer, it can depend on what your result should look like, i.e. are you okay to have
func_list = [lambda x: x+"A", lambda x: x+"B", lambda x: x+"C"]
print([f("str") for f in func_list])
print([f for f in map(lambda f: f("str"), func_list)])
from functools import partial
def apply(f, a):
return f(a)
print([f for f in map(partial(apply, a="str"), func_list)])
def map_funcs(obj, func_list):
return [func(obj) for func in func_list]
print(map_funcs("str", func_list))
In all those cases the result is:
['strA', 'strB', 'strC']
Upvotes: 1
Reputation: 133764
I think this should fit your 'functional' criteria, To answer your question, I don't think there is a clean way and you should just acclimatize to list comprehensions.
As suggested by @J.F.Sebastian
>>> from operator import methodcaller
>>> funcs = (lambda x: x + 1, lambda x: x + 2)
>>> obj = 5
>>> list(map(methodcaller('__call__', obj), funcs))
[6, 7]
Here is a crazy way of doing it:
>>> from itertools import starmap, repeat
>>> from types import FunctionType
>>> funcs = (lambda x: x + 1, lambda x: x + 2)
>>> obj = 5
>>> list(starmap(FunctionType.__call__, zip(funcs, repeat(obj))))
[6, 7]
As suggested by @AleksiTorhamo
>>> from itertools import repeat
>>> from types import FunctionType
>>> obj = 5
>>> funcs = (lambda x: x + 1, lambda x: x + 2)
>>> list(map(FunctionType.__call__, funcs, repeat(obj)))
[6, 7]
Upvotes: 12
Reputation: 441
The library toolz
has a compose
function that does just that.
from toolz import compose
compose(foo1, foo2)(obj)
Upvotes: 1
Reputation: 1
This is my solution:
def plus(i):
return i+i
def mult(i):
return i*4
functions = [plus,mult]
result=[]
for i in ["a","b","c","d"]:
for j in functions:
result.append(j(i))
result Out[16]: ['aa', 'aaaa', 'bb', 'bbbb', 'cc', 'cccc', 'dd', 'dddd']
Upvotes: 0
Reputation: 2272
The problem is the missing $
operator which is trivially defined by
def apply(f, a):
return f(a)
then one can do the currying ($ obj)
with a partial in python like this: partial(apply, a=obj)
having this we can do a map apply with
map(partial(apply, a=obj), [foo1, foo2]))
Upvotes: 9
Reputation: 104852
I think that list comprehensions are the best way to build one list based on another. Applying regular functions from a list is quite easy:
results = [f(obj) for f in funcList]
If you don't need the whole list of results at once, but only need to iterate over the items in one at a time, a generator expression may be better:
genexp = (f(obj) for f in funcList)
for r in genexp:
doSomething(r)
If your functions are methods, rather than basic functions there are two ways to go:
Using bound methods, in which case you don't need to provide the object at all when making the function calls:
obj = SomeClass()
funcList = [obj.foo1, obj.foo2]
results = [f() for f in funcList]
Or using unbound methods, which are simply regular functions that expect an instance of the class they are defined in as their first argument (conventionally named self
):
funcList = [SomeClass.foo1, SomeClass.foo2]
obj = SomeClass()
results = [f(obj) for f in funcList]
Of course, if you don't need to capture the results of the function, it is simplest to simply write a loop:
for f in funcList:
f(obj)
Upvotes: 5
Reputation: 978
You could always just create a function to take care of it for you:
def map_funcs(obj, func_list):
return [func(obj) for func in func_list]
# I was under the impression that the OP wanted to compose the functions,
# i.e. f3(f2(f1(f0(obj))), for which the line below is applicable:
# return reduce(lambda o, func: func(o), func_list, obj)
map_funcs(it, [Buy, Use, Break, Fix])
Upvotes: 19