0

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)
                 )
Rocketq
  • 5,423
  • 23
  • 75
  • 126

1 Answers1

2

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}]
)
N Chauhan
  • 3,407
  • 2
  • 7
  • 21
  • Nice suggestion, but I believe there is must be a way, maybe without wraping all functions https://albumentations.readthedocs.io/en/latest/examples.html – Rocketq Aug 06 '19 at 16:27
  • 1
    Please see my edit. You could easily change this so you input the data for the functions in different formats, but this seems clear enough. – N Chauhan Aug 06 '19 at 16:46