Search code examples
pythonpython-3.xfunction-declaration

How to compose (combine) several function calls with arguments


My full question sound this way: How to compose (combine) several function calls with arguments without using partial.

Right now current code below works fine, but I need to call partial every time any additional argument is provided:

    from functools import partial

    def pipe(data, *funcs):
        for func in funcs:
            data = func(data)
        return data

    def mult(data, amount=5, bias=2):
        return data*amount + bias

    def divide(data, amount, bias=1):
        return data/amount + bias

    result = pipe(5,
                    mult, 
                    partial(divide, amount=10)
                 )
    print(result)
    # 3.7

But I would like to partial to be called inside of pipe function. So calling should start looking this way:

    result = pipe(5,
                     mult, 
                     divide(amount=10)
                 )

Solution

  • Here's an implementation that may help you. It's a decorator to use on functions so that you can create partial function by calling the function itself, rather than partial(func, ...):

    from functools import partial, wraps
    
    def make_partial(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return partial(func, *args, **kwargs)
        return wrapper
    

    With any function that you want your behaviour with, decorate it like so:

    @make_partial
    def divide(data, amount, bias=1):
        return data/amount + bias
    

    Now you can call pipe as you describe as calling divide(amount=10) now returns a partial too.

    result = pipe(5, mult, divide(amount=10))
    

    It would be difficult to make the partial call within pipe as you would somehow need to pass in the desired function and its default arguments without calling it. This method tries to eliminate this and keep your existing functions clean.


    The only other way I can suggest is that you pass a list of functions, a list of lists of partial args and a list of dicts of keyword arguments. Your function signature would be pipe(data, funcs, args, kwargs).

    def pipe(data, funcs, args, kwargs):
        for func, arg_list, kwarg_list in zip(funcs, args, kwargs):
            data = partial(func, *arg_list, **kwarg_list)(data)
        return data
    

    To call pipe, it gets a bit more complicated:

    result = pipe(
        5,
        [mult, divide],
        [[], []],
        [{}, {'amount': 10}]
    )