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?

11
  • 2
    Does this answer your question? Choosing the right Design Pattern
    – gnat
    Commented Mar 16, 2020 at 19:26
  • Not at all... I'm not even sure OOP is the answer here....
    – user32882
    Commented Mar 16, 2020 at 19:39
  • 1. Off topic here 2. Don't use python
    – user348453
    Commented Mar 16, 2020 at 19:48
  • 1
    To attempt to assist in illustrating the question, the general problem is of managing functions which apply a complex calculation and would have a large number of parameters as part of that calculation. The problem is presented from a physics context, where common formulas can be complex and incorporate a lot of physical "constants" (that is, empirically defined values). The author wishes to make a distinction between parameters which he considers true variables, and these defined values which, ideally, he will not have to individually provide on each and every call of the function.
    – Steve
    Commented Mar 17, 2020 at 10:32
  • 1
    ...and additionally, the author does not want to employ programmatic constants or global variables. Instead, he wants at some level to define the core function in a pure fashion - providing all dependencies and defined values as parameters, along with providing what we'd more naturally think of as the variables - and thus retain the flexibility potentially to adjust or use a different set of defined values within the same program. Ultimately, he wants some happy medium between stating every parameter at the site of every call, and on the other hand not being able to adjust the defined values.
    – Steve
    Commented Mar 17, 2020 at 10:37

4 Answers 4

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
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

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.

1
  • 1
    I've edited my answer based on this; it's a little bit simpler - see if you can make some use of it. Just curious: why are you concerned about the solution being too Python-specific? Are you planning on doing something similar in another language? If so, some languages don't directly support passing functions as parameters, but they do have function pointers (or something similar) and lambdas, so you can make it work that way. Commented Mar 21, 2020 at 20:29
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.

3
  • Could you provide an example of what you mean? Some code perhaps?
    – user32882
    Commented Mar 17, 2020 at 5:20
  • 2
    @user32882, I'm not a Python user myself, so I'd struggle to provide any code off the cuff. If it has lambda functions, then Filip's answer above is a good alternative. If it has partial application (and it appears that it does), then you'd really be cooking on gas: learnpython.org/en/Partial_functions
    – Steve
    Commented Mar 17, 2020 at 6:35
  • Btw @user32882, fwiw, I can't see why your questions (including the one linked to, which was closed by force) have attracted such a torrent of downvotes. As I say though, beware the differences between mathematicians and programmers - the link you gave at the top is a prime example of something written by a mathematician. Amongst other things, programmers differ firstly by their understanding of terminology, secondly by their notation (which is tailored to the benefits and limitations of computers and standard keyboards), and thirdly by their concern for executability.
    – Steve
    Commented Mar 17, 2020 at 6:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.