这很难抽象地描述,所以我就给出一个简化的&剪掉)例子:
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)
是否有更好的方法来表明这些方法都属于同一个"家族",并允许在循环中调用它们而不必每次都命名它们?
我过去用过的另一种方法是用一个通用的前缀来命名方法,例如
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)
但这感觉更粗俗。
我也可以把每个方法都变成一个公共超类的实例,但这感觉有点过头了。
为什么不简单地将实际的方法存储在列表中,并完全避免调用getattr
呢?
>>> class SomeClass(object):
...
... def method_one(self):
... print("First!")
... return 0
...
... def method_two(self):
... print("Second!")
... return 1
...
... def method_three(self):
... print("Third!")
... return 2
...
... _METHODS = (method_one, method_two, method_three)
...
... def call_all(self):
... for method in SomeClass._METHODS:
... # remember that _METHODS contains *unbound* methods!
... print("Result: {}".format(method(self)))
...
>>> obj = SomeClass()
>>> obj.call_all()
First!
Result: 0
Second!
Result: 1
Third!
Result: 2
在其他一些语言中,可能会使用命令模式等设计模式,但这主要是因为这些语言没有第一类函数/方法对象。Python内置了这种模式
-
您可以使用类装饰器来生成指标列表方法。这样做的好处是,您可以生成在类定义时的度量方法列表而不是每次调用
printResults
时重新生成列表。另一个优点是您不必手动维护
ClassificationResults.metrics
列表。您不必在两个地方拼写方法的名称,因此它是DRY-er,如果您添加了另一个度量,则不必记住也要更新ClassificationResults.metrics
。你只需要给它一个以metrics_
开头的名字。 -
由于每个度量方法返回一个类似的对象,您可能会考虑在类中形式化这个概念(如下面的
Metric
)。一个这样做的好处是您可以定义一个__repr__
方法来处理如何打印结果。注意printResults
是多么简单(下图)。
def register_metrics(cls):
for methodname in dir(cls):
if methodname.startswith('metric_'):
method = getattr(cls, methodname)
cls.metrics.append(method)
return cls
class Metric(object):
def __init__(self, pos, total):
self.pos = pos
self.total = total
def __repr__(self):
msg = "{p} instances labelled positive. {t} of them correct (recall={d:.2g})"
dec = self.pos / float(self.total)
return msg.format(p=self.total, t=self.pos, d=dec)
@register_metrics
class ClassificationResults(object):
metrics = []
def metric_recall(self):
tpos, pos = 1, 2
return Metric(tpos, pos)
def metric_precision(self):
tpos, true = 3, 4
return Metric(tpos, true)
def printResults(self):
for method in self.metrics:
print(method(self))
foo = ClassificationResults()
foo.printResults()
所以基本上你想要消除getattr调用和在两个地方指定函数的需要。或者命令模式。
似乎是一个扩充可调用对象的合适例子,也许像这样:
class Command(object):
def __init__(self, function=None):
self._function = function
def function(self, *args):
return self._function(*args)
def name(self):
return self.function.func_name # Or other code for callables.
def __call__(self, *args):
return self.function(*args)
commands = []
def recall(my, args):
...
commands.append(Command(recall))
class Precision(Command):
def function(self, my, args):
...
commands.append(Precision)
和
results = [command() for command in commands]
或者
results = [(command.name(), command() for command in commands)]
或一个Runner:
class Runner(object):
def __init__(self, commands):
groupings = {}
for command in commands:
groupings.setdefault(command.__class__.__name__, []).append(command)
self.groupings = groupings
def run(self, group=None):
commands = self.groupings.get(group,[]) if group else itertools.chain(*self.groupings.values())
return [command() for command in commands]
啊,啊,啊,啊。
快速编写这段代码,因此可能有一两个错别字。
亚当