3

In trying to build a scientific Python package, I'm aiming to separate key functionality into different .py files for clarity. Specifically, it seems logical to me to split complicated numerical calculations for a model into one python file (say, "processing.py"),and plotting routines for their visualisation into another python file ("plotting.py").

For convenient and memory-efficient usage, the model class should be able to inherit all the plotting methods, making them easily accessible for the user, yet keeping the code easy to maintain and read by separating scientific numerical code from visualisation code.

I outline my vision for achieving this below, but am struggling with how to implement this in Python / if a better OOP style is available.

For example, in plotting.py:

class ItemPlotter(object):

    def visualisation_routine1(self):
        plt.plot(self.values)  # where values are to be acquired from the class in processing.py somehow

and in processing.py:

class Item(object):

    def __init__(self, flag):
        if flag is True:
            self.values = (1, 2, 3)
        else:
            self.values = (10, 5, 0)

        from plotting.py import ItemPlotter
        self.plots = ItemPlotter()

which results in the following command line usage:

 from processing.py import Item
 my_item = Item(flag=True)
 # Plot results
 my_item.plots.visualisation_routine1()

My actual code will be more complicated than this and potentially Item will have attributes that are large datasets, thus I expect that I need to avoid copying these for memory efficiency.

Is my vision possible, or even a Pythonic approach please? Any comments re. OOP or efficient ways to achieve this would be appreciated.

PS, I'm aiming for Py2.7 and Py3 compatibility.

IanRoberts
  • 2,846
  • 5
  • 26
  • 33
  • 5
    That's neither inheriting nor subclassing, that's *composing*. And yes, it's fine to use instances of classes from elsewhere as attributes, but you should still put the import at the top of the file. – jonrsharpe Jan 09 '17 at 11:24
  • Can you provide a working example please? When I try this, I get the AttributeError: 'ItemPlotter' object has no attribute 'vales'. Thanks for your help. – IanRoberts Jan 09 '17 at 11:29
  • 1
    Well... yes, that's right, it doesn't. Because, again, **you aren't subclassing**. You have an instance of `ItemPlotter` **inside** your instance of `Item`, but they aren't the same object (they don't share `self`). Perhaps `Item` *should* inherit from `ItemPlotter`, or vice versa. – jonrsharpe Jan 09 '17 at 11:30
  • 1
    Or, you could pass the instance of Item (ie `self`) either when you instantiate ItemPlotter, or when you call `visualisation_routine1`. – Daniel Roseman Jan 09 '17 at 11:31
  • Also note that creating e.g. `self.plotter = ItemPlotter(self.values)` doesn't actually *copy* the values, it just means the two instances share a reference to the same object. If you want `ItemPlotter` to be a "mix-in class", though, you need `Item` to inherit it. – jonrsharpe Jan 09 '17 at 11:32
  • Logically, I could imagine that ItemPlotter could inherit from Item,but then in order to execute ``self.plotter = ItemPlotter'' I would need to pass all the required args for Item(), which leads to length calculations in the Item's init routine to re-find the values I wish to plot. – IanRoberts Jan 09 '17 at 11:38
  • The idea of passing the instance of the Item: ``self.plotter = ItemPlotter(self)'' is interesting. Since this results in a shared memory reference, rather than copying the data, this should be memory efficient, right? – IanRoberts Jan 09 '17 at 11:40

1 Answers1

2

For convenient and memory-efficient usage, the model class should be able to inherit all the plotting methods, making them easily accessible for the user, yet keeping the code easy to maintain and read by separating scientific numerical code from visualisation code.

As Jon points out, you're actually describing composition rather than inheritance (which is the generally preferred way of doing it anyway).

One way of achieving what you want is to create an abstract class that defines the interface for an "item plotter" and inject it as a dependency to instances of Item. You can allow clients of Item to easily plot the data encapsulated by the Item instance by exposing a method that delegates plotting to the injected ItemPlotter.

This approach allows each class to respect the Single Responsibility Principle and makes the code easier to understand, maintain and test. You can define Item and ItemPlotter in separate Python modules if you prefer.

from abc import abstractmethod, ABC


class AbstractItemPlotter(ABC):
    @abstractmethod
    def plot(self, values):
        return


class ItemPlotter(AbstractItemPlotter):
    def plot(self, values):
        # plt.plot(values)
        print('Plotting {}'.format(values))
        pass


class Item(object):
    def __init__(self, flag: bool, plotter: AbstractItemPlotter):
        self._plotter = plotter
        if flag:
            self.values = (1, 2, 3)
        else:
            self.values = (10, 5, 0)

    def visualisation_routine1(self):
        self._plotter.plot(self.values)

if __name__ == '__main__':
    item = Item(flag=True, plotter=ItemPlotter())
    item.visualisation_routine1()

Output

Plotting (1, 2, 3)

Edit

This code was tested using Python 3.5.2. I can't say for sure if it will work in Python 2.7 as is.

Tagc
  • 8,736
  • 7
  • 61
  • 114
  • Thanks for this and the links to good OOP styles. This looks great. I wonder why we need to create the AbstractItemPlotter class though - do I need to replicate every method of ItemPlotter in the AbstractItemPlotter as an "abstractmethod" for this to work? Additionally, I wonder how you think this compares to the idea from @Daniel Roseman in the previous post comment re. simplying initialising the plotter class with self.plotter = ItemPlotter(self), and saving a reference to self in the ItemPlotter init? Thanks – IanRoberts Jan 09 '17 at 12:26
  • 1
    @IanRoberts "do I need to replicate every method of ItemPlotter in the AbstractItemPlotter as an "abstractmethod" for this to work?" Only the methods that are likely to vary between implementations. Common methods can be implemented in the base class. Also see [this post](http://stackoverflow.com/questions/1686174/when-should-one-use-interfaces). As for the second question, Daniel's approach seems like another decent way of doing it, although I don't know if he actually means to store the `ItemPlotter` within `Item`. That couples the classes a lot tighter, which is generally bad. – Tagc Jan 09 '17 at 12:29