代码之家  ›  专栏  ›  技术社区  ›  Joooeey

如果Python方法存在,则调用它

  •  4
  • Joooeey  · 技术社区  · 7 年前

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

    示例代码:

    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. 与2相同,但检查错误消息以确保它是正确的AttributeError
    4. 类似于2,但定义一个抽象的树皮方法 NotImplementedError 在抽象类中,检查 未实现错误 而不是 AttributeError .有了这个解决方案,派林会抱怨我忘了重写中的抽象方法 ScaryCat .
    5. 在抽象类中定义一个空树皮方法:

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

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

    3 回复  |  直到 7 年前
        1
  •  1
  •   wiesion    7 年前

    在我看来,你对继承的想法是错误的。基类应该封装在任何子类之间共享的所有内容。如果某个东西不是所有子类都共享的,根据定义,它不是基类的一部分。

    所以你说的“如果有人走过它会看起来很生气,如果可以,它会吠叫”对我来说毫无意义。“汪汪如果可以”部分不是在所有子类中共享的,因此不应该在基类中实现。

    什么 应该 发生的事情是,你想要吠叫的子类 加上 将此功能添加到 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() 方法将其作为超类的扩展功能的一部分 保护 方法

        2
  •  2
  •   bnaecker    7 年前

    我想是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)
    
        3
  •  1
  •   abarnert    7 年前

    你错过了一个可能性:

    定义一个 bark 提高 NotImplementedError ,就像你的选项4一样,但是 不要 把它抽象化。

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


    至于你的其他选择:

    • hasattr 是不必要的LBYL,通常不是肾盂。
    • 这个 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。我认为你提出的大多数玩具例子都是这样的。

    然而,我怀疑大多数现实生活中的例子会有很大不同:

    • 大多数现实生活中的例子不需要这种深层次的层次结构。
    • 通常是 树皮 将由所有子类实现,或者不会由超类调用。
    • 在那些确实需要的人中,我认为选择5通常是合适的。当然 树皮 沉默不是一件好事 斯卡里卡特 是的,但是 parse_frame 沉默是一种幸福 ProxyProtocol
    • 在那之后,几乎没有例外,所以很难抽象地、笼统地谈论它们。