timleathart
timleathart

Reputation: 560

How to create a function that applies a function to the inputs of another function in Python?

I'm looking for a nice functional way to do the following:

def add(x, y):
    return x + y

def neg(x):
    return -x

def c(x, y):
    # Apply neg to inputs for add
    _x = neg(x)
    _y = neg(y)

    return add(_x, _y)

neg_sum = c(2, 2)   # -4

It seems related to currying, but all of the examples I can find use functions that only have one input variable. I would like something that looks like this:

def add(x, y):
    return x + y

def neg(x):
    return -x

c = apply(neg, add)
neg_sum = c(2, 2)   # -4

Upvotes: 0

Views: 871

Answers (3)

smed
smed

Reputation: 158

From Matthias Fripp's answer, I asked myself : I'd like to compose add and neg both ways : add_neg(*args) and neg_add(*args). This requires hacking Matthias suggestion a bit. The idea is to get some hint on the arity (number of args) of the functions to compose. This information is obtained with a bit of introspection, thanks to inspect module. With this in mind, we adapt the way args are passed through the chain of funcs. The main assumption here is that we deal with real functions, in the mathematical sense, i.e. functions returning ONE float, and taking at least one argument.

from functools import reduce
from inspect import getfullargspec


def arity_one(func):
    spec = getfullargspec(func)
    return len(spec[0])==1 and spec[1] is None

def add(*args):
    return reduce(lambda x,y:x+y, args, 0)

def neg(x):
    return -x

def compose(fun1,fun2):
    def comp(*args):
        if arity_one(fun2): return fun1(*(map( fun2, args)))
        else: return fun1(fun2(*args))
    return comp


neg_add = compose(neg, add)
add_neg = compose(add, neg)

print(f"-2+(-3) = {add_neg(2, 3)}")
print(f"-(2+3) = {neg_add(2, 3)}")

The solution is still very adhoc...

Upvotes: 0

Matthias Fripp
Matthias Fripp

Reputation: 18625

This is a fairly direct way to do it:

def add(x, y):
    return x + y

def neg(x):
    return -x

def apply(g, f):
    # h is a function that returns
    # f(g(arg1), g(arg2), ...)
    def h(*args):
        return f(*map(g, args))
    return h

# or this:
# def apply(g, f):
#    return lambda *args: f(*map(g, args))

c = apply(neg, add)
neg_sum = c(2, 2)   # -4

Note that when you use *myvar as an argument in a function definition, myvar becomes a list of all non-keyword arguments that are received. And if you call a function with *expression as an argument, then all the items in expression are unpacked and sent as separate arguments to the function. I use these two behaviors to make h accept an unknown list of arguments, then apply function g to each one (with map), then pass all of them as arguments to f.

Upvotes: 2

JeffUK
JeffUK

Reputation: 4241

A different approach, depending on how extensible you need this to be, is to create an object which implements your operator methods, which each return the same object, allowing you to chain operators together in arbitrary orders.

If you can cope with it always returning a list, you might be able to make it work.

class mathifier:

    def __init__(self,values):
        self.values = values

    def neg(self):
        self.values = [-value for value in self.values]
        return self

    def add(self):
        self.values = [sum(self.values)]
        return self
         
print (mathifier([2,3]).neg().add().values)

And you can still get your named function for any set of chained functions:

neg_add = lambda x : mathifier(x).neg().add()

print(neg_add([2,3]).values)

Upvotes: 0

Related Questions