2

here's my problem:

given any two functions, eg. f(x,a) and g(x,b), I want to build a new function, say F(f,g), which returns the product of the f and g. So:

F(f,g) = f*g = f(x, a) * g(x, b) = F(x, a, b)

I want to do this hardcoding the least possible. So, for h(x, c, d), I would get F(f,h) = F(x, a, c, d). Given that then I want to minimize F, I thought of building a class. Here's a MWE:

import numpy as np
from inspect import getargspec

def f(x, a):
    return np.tanh(x*a)

def g(x, b):
    return np.power(x,b)

def h(x, c, d):
    return x*c+np.log(x)

class fit_func(object):
    def __init__(self, data, *args):
        self.data = data
        self.func_a = args[0]
        self.func_b = args[1]
        self.args_a = getargspec(args[0])[0][1:]
        self.args_b = getargspec(args[1])[0][1:]

at this point, I thought of including the following __call__ method:

    def __call__(self, *self.args_a, *self.args_b):
        return self.func_a(self.data,self.args_a)*self.func_b(data,self.args_b)

I thought: this way an instance of the class, say F = fit_func(some_data_array,f,g), would be callable as F(a,b). However, python doesn't like the self.args_a and self.args_b among the arguments of __call__ and I understand why. Does anybody know a clever way to obtain this? Thank you very much in advance

andrea
  • 525
  • 1
  • 5
  • 21
  • I believe that you have meant f(x, a) * g(x, *b*) = F(x, a, b) – Mike Nov 22 '16 at 12:49
  • absolutely Mike and I've also corrected one thing: the unpacking * in front of `self.args_a` and `b` in the argument of the `__call__` method – andrea Nov 22 '16 at 12:55

1 Answers1

0

If you just accept positional arguments you better to save the length of arguments for each functions, and then pas proper slices of args to each function in call method:

import numpy as np
from inspect import getargspec

class fit_func(object):
    def __init__(self, *args):
        self.func_a = args[0]
        self.func_b = args[1]
        self.size_arg_a = len(getargspec(self.func_a)[0])
        self.size_arg_b = len(getargspec(self.func_b)[0])
    def __call__(self, *args):
        return self.func_a(*args[:self.size_arg_a]) * self.func_b(*args[self.size_arg_b-1:])

Demo:

def f(x, a):
    return np.tanh(x*a)

def h(x, c, d):
    return x*c+np.log(x)

F = fit_func(f, h)
print(F(3, 4, 3, 5, 7))
16.0986122875

If you want to pass keyword arguments to final function:

import numpy as np
from inspect import getargspec
from operator import itemgetter


class fit_func(object):
    def __init__(self, *args):
        self.func_a = args[0]
        self.func_b = args[1]
        self.arg_a = getargspec(self.func_a)[0]
        self.arg_b = getargspec(self.func_b)[0]

    def __call__(self, **kwargs):
        arg_a = itemgetter(*self.arg_a)(kwargs)
        arg_b = itemgetter(*self.arg_b)(kwargs)
        return self.func_a(*arg_a) * self.func_b(*arg_b)

Demo:

def f(x, a):
    return np.tanh(x*a)

def h(x, c, d):
    return x*c+np.log(x)

F = fit_func(f, h)
print(F(x=3, a=4, c=5, d=7))
16.0986122875
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • what if I don't want positional arguments but instead to call the `__call__` method with kwdargs corresponding to the definition of the functions `f`, `g` and `h`? so `F(f,h) = F(a=...,c=...,d=...)` – andrea Nov 22 '16 at 13:18
  • this is almost it. in your updated version, the user needs to know what the arguments names of `f` and `h` are. in other terms: is there a way to obtain from `F(f,h)` that `getargspec(F)[0] = ['x', 'a' , 'c', 'd']` ? – andrea Nov 22 '16 at 13:45
  • @andrea Why not, just do `F.arg_a`. – Mazdak Nov 22 '16 at 16:48
  • I need a new function with a signature given by the extra parameters of the functions provided in the instantiation of `F` (eg. `a,b`) to be passed to iminuit. I cannot define it as `def New_func(F.arg_a, F.arg_b): ....`. I think the answer could be in [http://stackoverflow.com/q/1409295/4349869] but I'm not sure – andrea Nov 22 '16 at 17:34