Question

Suppose I have class Function, whose instances are callables that take one argument. I defined pointwise arithmetic for these classes in the straightforward way. Here's a simplified version of my code (I actually have more complex behavior in __init__ and __call__ but it's irrelevant for this question):

class Function:
  '''
  >>> f = Function(lambda x : x**2)
  >>> g = Function(lambda x : x + 4)
  >>> h = f/g
  >>> h(6)
  3.6
  '''
  def __init__(self, func):
    self.func = func
  def __call__(self, value):
    return self.func(value)
  def __truediv__(self, other):
    if isinstance(other, Function):
        return Function(lambda x:self(x)/other(x))
    else:
        return NotImplemented
  # ...

I'm stuck when I try to allow implicit type conversions. For example, I want to be able to write:

>>> f = Function(lambda x : x ** 2)
>>> g = f+1
>>> g(5)
26

In other words, whenever I see a numeric object v in an arithmetic expression next to a Function instance, I want to convert v to Function(lambda x : v).

In addition, I want to achieve similar behavior for some of my user-defined types (again, whenever I see them in the same binary arithmetic expression with a Function object).

While I can certainly code this logic with a brute force assortment of regular and reflected binary arithmetic operators, each checking isinstance(v, numbers.Number), and isinstance(v, MyUserDefinedType), I feel there might be a more elegant way.

Also, if there are any other improvements possible with my design, please let me know. (Function objects are created rarely, but called very often, so performance is of some interest.)

EDIT:

To address @Eric's comment, I should clarify that I have another user-defined class Functional:

class Functional:
  '''
  >>> c = [1, 2, 3]
  >>> f = Functional(lambda x : x + 1)
  >>> f(c)
  [2, 3, 4]
  >>> g = Functional(lambda x : x ** 2)
  >>> h = f + g
  >>> h(c)
  [3, 7, 13]
  '''
  def __init__(self, func):
    self.func = func
  @staticmethod
  def from_function(self, function):
    return Functional(function.func)
  def __call__(self, container):
    return type(container)(self.func(c) for c in container)
  def __add__(self, other):
    if isinstance(other, Functional):
      return Functional(lambda x : self.func(x) + other.func(x))
    else:
      return NotImplemented

When I see both a Function and a Functional instance in the same arithmetic expression, I want Function to be implicitly converted to Functional using Functional.from_function method.

So, implicit type conversion hierarchy goes like this:

  • Functional
  • Function
  • anything else

And I'd like to implicitly convert to the highest type in this hierarchy seen in a given arithmetic expression.

Was it helpful?

Solution

Something like this for all operators would work well:

def __truediv__(self, other):
  if callable(other):
      return Function(lambda x:self(x)/other(x))
  else:
      return Function(lambda x:self(x)/other)

OTHER TIPS

One option is to make all the operators in your Function class accept arbitrary values, which will be applied to the result of the underlying function if they're not functions themselves. For example, to extend allow f / 5, when f is a Function, simply modify the __truediv__ implementation you have to:

def __truediv__(self, other):
    if isinstance(other, Function):
        return Function(lambda x:self(x)/other(x))
    else:
        return Function(lambda x:self(x)/other)

You can optionally do some type checking to make sure that's sane (and raise errors early rather than later on), but it works without that.

After reading the comments and the other answers, I tried this approach. I'm posting it to ask for feedback. I like that I can handle both Function and Functional in one swoop, but I'm afraid it might be very expensive in terms of performance:

class Function:
    '''
    >>> f = Function(lambda x : x**2)
    >>> g = Function(lambda x : x + 4)
    >>> h = f/g
    >>> h(6)
    3.6
    >>> k = f + 1
    >>> k(5)
    26
    >>> m = f + (lambda x : x + 1)
    >>> m(5)
    31
    '''
    def __init__(self, arg):
        if isinstance(arg, Function):
            self.func = arg.func
        elif callable(arg):
            self.func = arg
        else:
            self.func = lambda x : arg
    def __call__(self, value):
        return self.func(value)
    def __truediv__(self, other):
        return self.__class__(lambda x:Function(self)(x)/Function(other)(x))
    def __rtruediv__(self, other):
        return self.__class__(lambda x:Function(other)(x)/Function(self)(x))
    def __add__(self, other):
        return self.__class__(lambda x:Function(self)(x)+Function(other)(x))
    def __radd__(self, other):
        return self.__class__(lambda x:Function(other)(x)+Function(self)(x))
    # ...


class Functional(Function):
    '''
    >>> c = [1, 2, 3]
    >>> f = Functional(lambda x : x + 1)
    >>> f(c)
    [2, 3, 4]
    >>> g = Functional(lambda x : x ** 2)
    >>> h = f + g
    >>> h(c)
    [3, 7, 13]
    '''
    def __call__(self, container):
        return type(container)(self.func(c) for c in container)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top