Recently I decided to start encapsulating my analysis into an object which has methods but also holds data from a data analysis.

This is conveninent since I’ve implemented classes to handle pipeline objects (projects, samples, annotation sheets).

Still, nothing particularly exciting:

from pipelines import Project

class Analysis(object):
    """
    Class to hold functions and data from analysis.
    """

    def __init__(self, data_dir, plots_dir, samples):
        self.data_dir = data_dir
        self.plots_dir = plots_dir
        self.samples = samples

    def do_some_work(self):
        ...
        self.results = results

prj = Project("testprj")
prj.addSampleSheet("metadata/sample_annotation.csv")

analysis = Analysis("data", "results/plots", prj.samples)
analysis.do_some_work()

Python decorators

It would be nice if each time I run a certain functions of the Analysis class the class itself would be saved as a Python pickle.

Enter Python decorators.

If you’re new to Python or Python decorators (I’ve known them for a while but seldomly use them) here’s a really nice introduction to nested functions, clojures and decorators.

In this case I write a decorator which calls the function and then performs its action, in this case, pickling the Analysis object:

# decorator for some methods of Analysis class
def pickle_me(function):
    def wrapper(obj):
        function(obj)
        pickle.dump(obj, open("analysis.pickle", 'wb'))
    return wrapper

To apply the decorator the some methods of the class, just add @pickle_me to the method:

class Analysis(object):
    ...

    @pickle_me
    def do_some_work(self):
        ...
        self.results = results

This works however, for functions accepting only self as argument as in the example. To allow an arbitrary number of arguments passed to each function, we will make the decorator function accept one argument which will be the Analysis (I keep calling it obj) object and any number of arguments (by using *args), which will be passed to the function that is decorated.

If I give the Analysis class an attribute holding where it should be pickled (pickle_file), then I can tell the decorator function to get it from the class itself:

# decorator for some methods of Analysis class
def pickle_me(function):
    def wrapper(obj, *args):
        function(obj, *args)
        pickle.dump(obj, open(obj.pickle_file, 'wb'))
    return wrapper

class Analysis(object):
    """
    Class to hold functions and data from analysis.
    """

    def __init__(self, pickle_file, **kwargs):
        self.pickle_file = pickle_file

    @pickle_me
    def do_some_work(self):
        pass

    ...
blog comments powered by Disqus