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