It's hard to describe this in the abstract, so let me just give a (simplified & snipped) example:
class ClassificationResults(object):
#####################################################################################################################
# These methods all represent aggregate metrics. They all follow the same interface: they return a tuple
# consisting of the numerator and denominator of a fraction, and a format string that describes the result in terms
# of that numerator, denominator, and the fraction itself.
#####################################################################################################################
metrics = ['recall', 'precision', 'fmeasure', 'f2measure', 'accuracy']
# ...
def recall(self):
tpos, pos = 0, 0
for prediction in self.predictions:
if prediction.predicted_label == 1:
pos += 1
if prediction.true_label == 1:
tpos += 1
return tpos, pos, "{1} instances labelled positive. {0} of them correct (recall={2:.2})"
def precision(self):
tpos, true = 0, 0
for prediction in self.predictions:
if prediction.true_label == 1:
true += 1
if prediction.predicted_label == 1:
tpos += 1
return tpos, true, "{1} positive instances. We labelled {0} correctly (precision={2:.2})"
# ...
def printResults(self):
for methodname in self.metrics:
(num, denom, msg) = getattr(self, methodname)()
dec = num/float(denom)
print msg.format(num, denom, dec)
Is there a better way to indicate that these methods all belong the same 'family', and to allow them to be called in a loop without naming them every time?
Another way I've done it in the past is to name methods with a common prefix, e.g.
def metric_precision(self):
tpos, true = 0, 0
for prediction in self.predictions:
if prediction.true_label == 1:
true += 1
if prediction.predicted_label == 1:
tpos += 1
return tpos, true, "{1} positive instances. We labelled {0} correctly (precision={2:.2})"
# ...
def printResults(self):
for methodname in dir(self):
meth = getattr(self, methodname)
if methodname.startswith('metric_') and callable(meth):
(num, denom, msg) = getattr(self, methodname)()
dec = num/float(denom)
print msg.format(num, denom, dec)
But this feels even more hackish.
I could also turn each method into an instance of a common superclass, but this feels like overkill.