1

I have been experimenting with the idea of function classes as explained in this article and Composition applied to function dependencies as described in the following questions:

Consider the following mathematical functions, where x is a variable and a, b and c are constants:

f(x) = a*x**2+b*x+c
g(x) = a*sin(3*x)

As python function classes these can be expressed as follows:

import numpy as np

class F:
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
    def __call__(self, x):
        return self.a*x**2+self.b*x+self.c

class G:
    def __init__(self, a):
        self.a = a
    def __call__(self, x):
        return self.a*np.sin(3*x)

For several reasons which I prefer not to elaborate here, I prefer to have one meta class which can be instantiated and assigned any function. The difficulty is of course the constants as self is undefined outside of a class. Here's what I mean:

def f(x):
    return a*x**2+b*x+c

F = FunctionClass(f)
#how to make a, b and c instance variables?

If I don't do this I am condemned to writing a class for every function, which defeats the purpose of what I am trying to achieve. I've asked a question addressing this on a more technical level in stack over flow but it was not very popular at all

Is there a design pattern or python construct that can allow me to achieve this? Perhaps decorators? Perhaps use of the __dict__ attribute? Any ideas?

user32882
  • 267

4 Answers4

8

Create a function that takes in your parameters and returns a lambda:

def get_quad_func(a, b, c):
    return lambda x: a*x*x + b*x + c

f1 = get_quad_func(2, 0, -1) 
f2 = get_quad_func(1, -1, 3)

Then use like any other function:

x = f1(0)

Side note: for small powers it's better to use x*x or x*x*x rather then x**2, x**3, etc.

For completeness, here's a simpler version of the solution found by the OP that works pretty much the same way. I'm reproducing the function dependency tree here for clarity. The Function class is replaced by the Function wrapper function.

enter image description here

import numpy as np

# utility function
def Function(func, **kwargs):
  return lambda x: func(x=x, **kwargs)

# user defined functions
def root(x, B, A):
    return B(x)+A(x)

def A(x,y,z):
    return x*np.log(y)+y**z

def B(alpha, x, y):
    return alpha(x)*y

def alpha(x,y,w):
    return x*y+w

if __name__=='__main__':
    x, y, z, w = 1, 2, 3, 4

    alpha = Function(alpha, y=y, w=w)
    A = Function(A, y=y, z=z)
    B = Function(B, alpha=alpha, y=y)
    root = Function(root, A=A, B=B)

    """
    Note that sometimes it may be desireable to use 
    different names for the wrapped functions, to 
    avoid shadowing the original user-defined ones.
    E.g., for this kind of thing to work:

    alpha1 = Function(alpha, y=1, w=2)
    alpha2 = Function(alpha, y=5, w=10) 
    B1 = Function(B, alpha=alpha2, y=2) 
    """

    print(root(x))

Like the original approach in the OPs answer, this requires the free argument to be named x; this can be mitigated by utilizing a technique similar to the modified __call__ method outlined in the same answer:

# utility function
def Function(func, **kwargs):
  param_key = [k for k in func.__code__.co_varnames if k not in kwargs][0]
  def wrapper(x):
    kwargs[param_key] = x
    return func(**kwargs)
  return wrapper
Robert Harvey
  • 200,592
1

This is going to look rather like functools.partial, because it basically is.

You construct an expression by binding other expressions either positionally, or by name, and then can evaluate the whole lot when you have the remaining parameters.

def partial_expression(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = { name: f(*fargs, **fkeywords) for name, f in keywords.items() }
        newkeywords.update(fkeywords)
        newargs = [arg(*fargs, **fkeywords) for arg in args]
        newargs.extend(fargs)
        return func(*newargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

def constant(a):
    def inner(*args, **kwargs):
        return a
    inner.args = [a]
    return inner

Taking an example from one of the linked question, you could define expressions

import numpy as np

Alpha = partial_expression(lambda x, y, w, *_, **__: x * y + w)
A = partial_expression(lambda x, y, z, *_, **__: x * np.log(y) + y**z)
B = partial_expression(lambda alpha, y, *_, **__: alpha * y, Alpha)
root = partial_expression(lambda b, a, *_, **__: b + a, B, A)

if __name__ == '__main__':
    x,y,z,w = 1,2,3,4
    print(root(**locals()))

I've used lambdas here to avoid duplicate names. _ and __ receive the extra arguments

Caleth
  • 12,190
1

Here's a nice, clean solution making use of partial and allowing for the kind of flexibility I am looking for. In the main code I am working out the function dependency outlined in this question. I add the graphic below for clarity:

enter image description here

Here's the code that solves the problem forx, y, z, w = 1, 2, 3, 4:

from functools import partial
import numpy as np

class Function:
    def __init__(self, func, **kwargs):
        self.__dict__.update(**kwargs)
        self._keys = kwargs.keys()
        self.func = func
    @property
    def f(self):
        kwargs = {k: self.__dict__[k] for k in self._keys}
        return partial(self.func, **kwargs)

    def __call__(self, x):
        return self.f(x=x)

def root(x, B, A):
    return B(x)+A(x)

def A(x,y,z):
    return x*np.log(y)+y**z

def B(alpha, x, y):
    return alpha(x)*y

def alpha(x,y,w):
    return x*y+w

if __name__=='__main__':
    x, y, z, w = 1, 2, 3, 4
    alpha = Function(alpha, y=y, w=w)
    A = Function(A, y=y, z=z)
    B = Function(B, alpha=alpha, y=y)
    root = Function(root, A=A, B=B)
    print(root(x))

The Composite pattern is implicit here in the kwargs dict as the values of the dict can either be terminal expressions (a floating point value) or nonterminal expressions (another Function class instance). For functools.partial this makes no difference, as arguments can contain any python object, so long as they work within the function.

There are however a couple of disadvantages:

  • the variable argument must be called x as shown by line 15 in my code. This is a limitation of functools.partial as clarified in a related question. There are ways around this (i.e by using positional arguments) but they have their own disadvantages, such as forcing the user to be careful about positional placement of the arguments in the passed native functions (root, A, B and alpha). I prefer to allow any positional variable order but force the user to stick with x as the name of the variable argument
  • The variable x must be carried through in all user defined functions.
  • Enforcing x as a variable argument is ensured only by naming it correctly. There is no programmatic construct that ensures this.

All in all, I am happy with this solution. Despite its weaknesses it allows me to build function trees which would otherwise be super tedious to do. Special thanks to @Steve and all those who believed this was worth investigating!

To circumvent the above disadvantages the __call__ method can be written as:

def __call__(self, x):
    all_args = set(self.func.__code__.co_varnames)
    constants = set(self._keys) # constants
    diff = all_args-constants
    arg = {list(diff)[0]:x}
    return self.f(**arg)

I am a bit concerned that this solution is very python specific but I'll solve non-python related problems when I run into them.

Robert Harvey
  • 200,592
user32882
  • 267
0

In my experience, mathematicians and programmers live on different planets.

The instance constructor here is being used to parameterise the functions by the back door.

You need a class for every defined set of "constants" (that is, the variables which are used within the function but not passed as parameters), and an instance for every specific combination of "constant" values which are used in a particular context.

In some circumstances this approach may be legitimate, but for purely mathematical work at this level of complexity, I would consider defining a structure (or whatever it's called in Python) to hold the multiple constants required, and then pass this structure as one argument to the function.

This means you don't keep having to restate the values of the constants explicitly in each calling statement (you can store the values as a set within a variable and then reuse the variable for multiple calls), or clutter the method signature with multiple parameters to pass all the "constants", but doesn't require the heavy overhead of one class per function.

Steve
  • 12,325
  • 2
  • 19
  • 35