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

时间:2013-05-31 19:33:02

标签: python design-patterns introspection

很难用抽象的方式来描述这个,所以让我给出一个(简化的& snipped)示例:

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)

但这感觉更加苛刻。

我也可以将每个方法变成一个普通超类的实例,但这感觉就像是矫枉过正。

3 个答案:

答案 0 :(得分: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

在其他一些语言中,可能会使用command pattern之类的设计模式,但这主要是因为这些语言没有第一类函数/方法对象。 Python内置了这种模式。

答案 1 :(得分: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()

答案 2 :(得分: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]

Yadda yadda yadda。

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

亚当