Is there a chain calling method in Python?

Question:

Is there a function in Python that would do this:

val = f3(f2(f1(arg)))

by typing this (for example):

val = chainCalling(arg,f3,f2,f1)

I just figured, since python is (arguably) a functional language the function I’m looking for will make syntax brighter

Asked By: Shai Ben-Dor

||

Answers:

Use the reduce() function to chain calls:

from functools import reduce

val = reduce(lambda r, f: f(r), (f1, f2, f3), arg)

I used the forward-compatible functools.reduce() function; in Python 3 reduce() is no longer in the built-in namespace.

This can also be made a separate function, of course:

from functools import reduce

def chain(*funcs):
    def chained_call(arg):
        return reduce(lambda r, f: f(r), funcs, arg)

    return chained_call
Answered By: Martijn Pieters

You can use the reduce() functool — as Martijn briantly suggested, or you can write it yourself quite simply:

def chainCalling(arg, *funcs):
    if len(funcs) > 0:
        return chainCalling(funcs[0](arg), funcs[1:])
    return arg

or, as an alternative not using recursion — so not bound to the call stack limitation, as suggested by Martijn:

def chainCalling(arg, *funcs):
    result = arg
    for f in funcs:
        result = f(result)
    return result

Obviously, you’ll want to call it that way, to avoid an useless reversal of the arguments:

chainCalling(arg, f1, f2, f3)
Answered By: zmo

It might be a tad late, but try my solution below. chain_func essentially creates a new wrapper function that calls all underlying functions in chain when invoked in runtime.

def chain_func(*funcs):
    def _chain(*args, **kwargs):
        cur_args, cur_kwargs = args, kwargs
        ret = None
        for f in reversed(funcs):
            cur_args, cur_kwargs = (f(*cur_args, **cur_kwargs), ), {}
            ret = cur_args[0]
        return ret

    return _chain
Answered By: Kang Min Yoo

In case you want to apply the chain of functions to multiple arguments, you can create an aggregated function.

g = lambda x: f3(f2(f1(x)))

or more flexible (when there is an arbitrary list of functions):

from functools import reduce, partial

f3 = lambda x: -x
f2 = lambda x: x ** 2
f1 = lambda x: x + 1
function_list = (f1, f2, f3)
g = partial(reduce, lambda r, f: f(r), function_list)
print(g(3))  # results in -16
Answered By: rwitzel

I feel like these answers are correct, but do not address the spirit of the question. We can get the same behavior by creating an intermediate "chained" function definition, and we can avoid confusion about the meaning of nested labmdas by defining a function that produces the chained function.

I have used g(f(x)) here so that the functions are applied from left to right.

from functools import reduce
from typing import Iterable, Callable, Any

def chain_fns(fns: Iterable[Callable[[Any], Any]]) -> Callable[[Any], Any]:                                                                     
    return reduce(lambda f, g: lambda x: g(f(x)), fns)

chained_method = chain_fns([f1, f2, f3])
chained_method(arg)
Answered By: John Haberstroh
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.