调用Python方法(如果它存在)

时间:2018-06-04 23:59:25

标签: python inheritance python-2.x

如果子类支持该方法,如何从基类方法调用子类方法?最好的方法是什么?举例说明,我有一种保护我家的动物:如果有人走过它会看起来很生气,如果可以的话就会吠叫。

示例代码:

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. 中的任何属性错误
  4. 与2相同,但检查错误消息以确保它是正确的AttributeError
  5. 与2类似,但定义了一个抽象的bark方法,它在抽象类中引发NotImplementedError并检查NotImplementedError而不是AttributeError。有了这个解决方案,Pylint会抱怨我忘记覆盖ScaryCat中的抽象方法。
  6. 在抽象类中定义一个空树皮方法:

    class Protector(object):
        def protect(self):
            self.lookangry()
            self.bark()
        def bark(self):
            pass
    
  7. 我在Python中认为他们通常应该是一种做某事的方法。在这种情况下,我不清楚哪个。这些选项中哪一个最具可读性,最不可能在更改内容时引入错误,并且最符合编码标准,尤其是Pylint?是否有更好的方法来做到我错过了?

3 个答案:

答案 0 :(得分:2)

在我看来,你正在考虑继承错误。基类应该封装在任何子类之间共享的所有内容。如果某些内容未被所有子类共享,则根据定义它不属于基类。

所以你的陈述“如果有人走过它会看起来很生气,如果它可以”吠叫“对我来说没有意义。 “bark if can can”部分不会在所有子类之间共享,因此不应在基类中实现。

应该发生的是您要强制的子类将此功能添加到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()

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

答案 1 :(得分:1)

你错过了一种可能性:

定义一个引发bark的{​​{1}}方法,如同您的选项4一样,但将其抽象化。

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

至于你的其他选择:

  • NotImplementedError是不必要的LBYL,通常不是Pythonic。
  • 可以通过在hasattr块内执行except来处理bark = self.bark问题,然后在try通过时执行bark()。这有时是必要的,但它有点笨拙并且没有“修复”的事实应该会让你知道它的价值多久。
  • 检查错误消息是一种反模式。任何不是单独的,记录在案的参数值的东西都可能会在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所做的事情。
  • 之后剩下的例外很少,很难抽象而且一般地谈论它们。

答案 2 :(得分:1)

我认为6.)可能是Protector类只使得抽象所需的基本共享方法,同时将额外的方法留给其继承人。当然,这可以分成更多的子类,参见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)