一种模式,其中一个类有许多相似的方法(相同的类型签名,相似的语义)



这很难抽象地描述,所以我就给出一个简化的&剪掉)例子:

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]

啊,啊,啊,啊。

快速编写这段代码,因此可能有一两个错别字。

亚当

最新更新