SlimJim
SlimJim

Reputation: 2272

Apply list of functions on an object in Python

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

Answers (7)

Hendrik F
Hendrik F

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

jamylak
jamylak

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

Jiř&#237; Mauritz
Jiř&#237; Mauritz

Reputation: 441

The library toolz has a compose function that does just that.

from toolz import compose
compose(foo1, foo2)(obj)

Upvotes: 1

Breno Bel&#233;m
Breno Bel&#233;m

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

SlimJim
SlimJim

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

Blckknght
Blckknght

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

ladaghini
ladaghini

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

Related Questions