0

so I have a set of distance functions and respective calculations, e.g. average, comparison

and I want to be able to iterate over those different distances to compute their values/averages/whatevers, and make it easy to add new distances

Right now, I'm doing that by using nested dictionaries, however this depends on all the functions existing and working properly, so I was wondering whether there is a design pattern that solves that?

My first idea was a metaclass that defines which functions need to exist and classes that implement these functions. However, then there would be no meaningful instances of those Distance classes. My second idea then was defining a Distance class and have the functions as attributes of that class, but that seems bad style. Example for the second Idea:

class Distance:
    def __init__(self, distf, meanf):
        self.distf = distf
        self.meanf = meanf

    def dist(self, x1,x2):
        return self.distf(x1,x2)

    def mean(self, xs):
         return self.meanf(xs)

    d = Distance(lambda x,y: abs(x-y), np.mean)

    d.dist(1,2) ##returns 1 as expected
    d.dist([1,2]) ## returns 1.5 as expected

this works (and also enforces the existence/maybe even properties of the functions), but as stated above feels like rather bad style. I do not plan to publish this code, its just about keeping it clean and organized if that is relevant. I hope the question is clear, if not pleas dont hesitate to comment and I will try to clarify.

EDIT: - @victor: Everything should be initially set. At runtime only selection should occur. - @abarnert Mostly habitual, also to restrict usage (np.mean needs to be called without axis argument in this example), but that should hopefully not be relevant since I'm not publishing this - @juanpa gonna look into that

  • Do you need to add new "types of space" (i.e. sets of distance and aux functions like average, comparison, etc.) dynamically at runtime or all the "spaces" are initially set in a program? – Victor Aug 14 '18 at 15:49
  • Is there a reason these need to look like methods, instead of just calling `d.distf` directly to call the function you stashed on `d`? – abarnert Aug 14 '18 at 15:51
  • "My first idea was a metaclass that defines which functions need to exist and classes that implement these functions." Perhaps you want the `abc` module? – juanpa.arrivillaga Aug 14 '18 at 15:52
  • Can you give an idea of what you plan to use this for? Having a class whose instances holds a collection of functions isn’t necessary a bad thing, but it really depends on how you’re going to be using it. Does the idea of “a distance” make sense as a conceptual thing in your program? Should “a distance” have a dist method that takes two numbers? If so, then this is the right model. – abarnert Aug 14 '18 at 16:28

1 Answers1

2

It seems that simple inheritance is what you need. So, you create a base class BaseSpace which is basically an interface:

from abc import ABC

class BaseSpace(ABC):
    @staticmethod
    def dist(x1, x2):
        raise NotImplementedError()

    @staticmethod
    def mean(xs):
        raise NotImplementedError()

Then you just inherit this interface with all different combinations of the functions you need, implementing the methods either inside the class (if you are using them once only) or outside, and just assigning them in the class definition:

class ExampleSpace(BaseSpace):
    @staticmethod
    def dist(x1, x2):
        return abs(x1 - x2)

    mean = staticmethod(np.mean)

Because of the Python's duck typing approach (which is also applicable to interface definition) you don't really need the base class actually defined, but it helps to show what is expected of each of your "Space" classes.

Victor
  • 418
  • 2
  • 10
  • 1
    Your `BaseSpace` should probably be an [abstract base class](https://docs.python.org/3/library/abc.html), to prevent people from instantiating it by accident, and to force its concrete children to implement its methods. – Patrick Haugh Aug 14 '18 at 18:28
  • Updated the class to inherit from `ABC`. Not sure if forcing children to implement all methods is required - I guess some spaces can miss some of the methods. – Victor Aug 14 '18 at 20:51