0

很难用抽象来描述这一点,所以让我举一个(简化和剪断的)例子:

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)

但这感觉更加骇人听闻。

我也可以将每个方法变成一个公共超类的实例,但这感觉有点过头了。

4

3 回答 3

2

为什么不简单地将实际方法存储在列表中并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 内置了这种模式。

于 2013-05-31T20:36:56.743 回答
1
  • 您可以使用类装饰器来生成度量方法列表。这样做的好处是您可以在类定义时生成度量方法列表,而不是每次 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()

于 2013-05-31T20:56:24.143 回答
0

所以你基本上想消除 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)]

或跑步者:

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]

亚达亚达亚达。

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

亚当

于 2013-05-31T20:01:46.050 回答