调用 Python 方法(如果存在)

call Python method if it exists

如何从基本 class 方法调用 subclass 方法,前提是 subclass 支持该方法?最好的方法是什么?举个例子,我有一只保护我家的动物:如果有人经过它会生气,它会吠叫。

示例代码:

class Protector(object):
    def protect(self):
        self.lookangry()
        if hasattr(self, 'bark'):
            self.bark()

class GermanShepherd(Protector):
    def lookangry(self):
        print u') _ _ __/°°¬'
    def bark(self):
        print 'wau wau'

class ScaryCat(Protector):
    def lookangry(self):
        print '=^..^='

我可以为此想到很多替代实现:

  1. 如上所述使用 hasattr
  2. try: self.bark() except AttributeError: pass 但这也捕获了 bark
  3. 中的任何 AttributeErrors
  4. 与 2 相同,但检查错误消息以确保它是正确的 AttributeError
  5. 与 2 类似,但定义了一个抽象的树皮方法,该方法在抽象 class 中引发 NotImplementedError 并检查 NotImplementedError 而不是 AttributeError。使用此解决方案,Pylint 会抱怨我忘记重写 ScaryCat.
  6. 中的抽象方法
  7. 在摘要中定义一个空的bark方法class:

    class Protector(object):
        def protect(self):
            self.lookangry()
            self.bark()
        def bark(self):
            pass
    

我在 Python 中认为它们通常应该是做某事的一种方式。在这种情况下,我不清楚是哪个。这些选项中的哪一个最易读,最不可能在内容更改时引入错误并且最符合编码标准,尤其是 Pylint?有没有我错过的更好的方法?

您错过了一种可能性:

定义一个引发 NotImplementedErrorbark 方法,如您的选项 4 所示,但 不要 将其抽象化。

这消除了 PyLint 的抱怨——更重要的是,消除了它抱怨的合法问题。


至于你的其他选择:

  • hasattr是不必要的LBYL,通常不是Pythonic。
  • except 问题可以通过在 try 块中执行 bark = self.bark 来解决,如果通过则执行 bark()。这有时是必要的,但事实上它有点笨拙而且还没有 "fixed" 应该让你知道它值得多久做一次。
  • 检查错误消息是一种反模式。任何不是单独记录的参数值的内容都可能在 Python 版本和实现中发生变化。 (另外,如果 ManWithSidekick.bark()self.sidekick.bark() 会怎样?你会如何区分那里的 AttributeError?)

所以,剩下 2、4.5 和 5。

我认为在大多数情况下,4.5 或 5 都是正确的选择。它们之间的区别不是实用的,而是概念上的:如果 ScaryCat 一种无声吠叫的动物,请使用选项 5;如果不是,那么吠叫必须是保护的可选部分,并非所有保护者都这样做,在这种情况下使用选项 4.5。

对于这个玩具示例,我想我会使用选项 4.5。而且我认为您提出的大多数玩具示例都是这种情况。

但是,我怀疑大多数现实生活中的例子会大不相同:

  • 大多数现实生活中的示例不需要这种深层次结构。
  • 通常 bark 会被所有子类实现,或者不会被超类调用。
  • 在那些确实需要这个的人中,我认为选项 5 通常适合。当然,bark静默不是 ScaryCat 做的事,但是 parse_frame 静默是 ProxyProtocol 做的事。
  • 而且在那之后剩下的例外非常少,很难抽象和笼统地谈论它们。

在我看来,您对继承的看法不正确。基础 class 应该封装在任何子 class 之间共享的所有内容。如果某些东西不被所有子 class 共享,根据定义它不是基础 class.

的一部分

所以你的说法 "if someone walks by it will look angry, and it will bark if it can" 对我来说没有意义。 "bark if it can" 部分并未在所有子 class 中共享,因此不应在基础 class.

中实现

应该发生的是你想要吠叫的子class 添加这个功能到protect() 方法。如:

class Protector():
    def protect(self):
        self.lookangry()

class GermanShepherd(Protector):
    def protect(self):
        super().protect() # or super(GermanShepherd, self).protect() for Python 2
        self.bark()

这样,所有子classes 都将lookangry(),但是实现bark() 方法的子classes 将把它作为扩展功能的一部分superclass 的 protect() 方法。

我认为 6.) 可能是 Protector class 只使所需的基本共享方法抽象化,而将额外的方法留给其继承者。当然这个可以拆分成更多的子class,见https://repl.it/repls/AridScrawnyCoderesource(写在Python 3.6)

class Protector(object):
  def lookangry(self):
    raise NotImplementedError("If it can't look angry, it can't protect")

  def protect(self):
      self.lookangry()


class Doggo(Protector):
  def bark(self):
    raise NotImplementedError("If a dog can't bark, it can't protect")

  def protect(self):
    super().protect()
    self.bark()


class GermanShepherd(Doggo):

  def lookangry(self):
    print(') _ _ __/°°¬')

  def bark(self):
    print('wau wau')


class Pug(Doggo):
  # We will not consider that screeching as barking so no bark method
  def lookangry(self):
    print('(◉ω◉)')


class ScaryCat(Protector):
  def lookangry(self):
      print('o(≧o≦)o')


class Kitten(Protector):
  pass


doggo = GermanShepherd()
doggo.protect()

try:
  gleam_of_silver = Pug()
  gleam_of_silver.protect()
except NotImplementedError as e:
  print(e)

cheezburger = ScaryCat()
cheezburger.protect()

try:
  ball_of_wool = Kitten()
  ball_of_wool.protect()
except NotImplementedError as e:
  print(e)