【问题标题】:How can I ensure that one of my class's methods is always called even if a subclass overrides it?即使子类覆盖它,如何确保始终调用我的类的方法之一?
【发布时间】:2011-11-01 17:16:42
【问题描述】:

例如,我有一个

class BaseHandler(object):
    def prepare(self):
        self.prepped = 1

我不希望每个继承 BaseHandler 并且还想实现 prepare 的人都必须记住调用

super(SubBaseHandler, self).prepare()

即使子类也实现了prepare,有没有办法确保超类方法运行?

【问题讨论】:

  • 简短的回答是你不能。如果它被覆盖,则需要成员查找算法来返回覆盖的版本。一种更强大的方法是不需要覆盖这些重要部分的设计,尽管这并不总是可能的。或者,编写代码,使其在未调用您的版本时引发错误或产生明显错误的结果,以帮助调试。
  • @delnan:使用元类不会依赖于实现(但很可能被认为是一种 hack,至少如果用于此目的)。
  • @Sven:我可以发誓有一个“可能”,特别是因为我不相信自己会列举所有的可能性。但我还是修改了我的评论。
  • @Sven 我认为元类在这里没有任何帮助。
  • @Lucho:嗯,我可以看到他要去哪里 - 看看子类是否覆盖了方法,如果是这样,用一个首先调用基类版本的版本包装它。不完美(可以在创建类后再次覆盖,可能导致人们手动调用基类实现,尽管它已经为他们完成了),但适度健壮。

标签: python subclass


【解决方案1】:

我已经使用元类解决了这个问题。

使用元类可以让BaseHandler 的实现者确保所有子类都将调用超类prepare(),而无需对任何现有代码进行调整。

元类在两个类上查找prepare 的实现,然后用调用superclass.prepare 后跟subclass.prepare 的子类prepare 覆盖。

class MetaHandler(type):
    def __new__(cls, name, bases, attrs):
        instance = type.__new__(cls, name, bases, attrs)
        super_instance = super(instance, instance)
        if hasattr(super_instance, 'prepare') and hasattr(instance, 'prepare'):
            super_prepare = getattr(super_instance, 'prepare')
            sub_prepare = getattr(instance, 'prepare')
            def new_prepare(self):
                super_prepare(self)
                sub_prepare(self)
            setattr(instance, 'prepare', new_prepare)
        return instance


class BaseHandler(object):
    __metaclass__ = MetaHandler
    def prepare(self):
        print 'BaseHandler.prepare'


class SubHandler(BaseHandler):
    def prepare(self):
        print 'SubHandler.prepare'

使用它看起来像这样:

>>> sh = SubHandler()
>>> sh.prepare()
BaseHandler.prepare
SubHandler.prepare

【讨论】:

    【解决方案2】:

    告诉您的开发人员定义prepare_hook 而不是prepare,但是 告诉用户拨打prepare

    class BaseHandler(object):
        def prepare(self):
            self.prepped = 1
            self.prepare_hook()
        def prepare_hook(self):
            pass
    
    class SubBaseHandler(BaseHandler):
        def prepare_hook(self):
            pass
    
    foo = SubBaseHandler()
    foo.prepare()
    

    如果您想要更复杂地链接来自多个子类的 prepare 调用,那么您的开发人员应该真正使用 super,因为这正是它的用途。

    【讨论】:

    • 只要层次结构不超过一层,它就可以正常工作,但考虑到它似乎是最好的解决方案。
    • 如果告诉用户定义一个特殊的方法很好,那么告诉他们在覆盖它时简单地调用基方法也很好。而且你不会引入类似的其他问题。
    • 虽然这可行,但我应该指定我希望子类化的人能够使用名称 prepare 而不是 prepare_hook。实际用例是我的 BaseHandler 是 Tornado RequestHandler 的子类。由于开发人员在编写龙卷风处理程序时可能熟悉使用 prepare 方法,我希望他们能够使用相同的方法名称,即使他们从我的 BaseHandler 类继承。元类解决方案更适合我的用例。
    【解决方案3】:

    只需接受您必须告诉子类化您的类的人在覆盖它时调用基方法。其他所有解决方案要么要求您解释他们做其他事情,要么涉及一些也可以规避的非 Python 黑客。

    Python 的对象继承模型被设计为开放的,任何尝试其他方式的尝试都只会使问题变得过于复杂,而实际上并不存在的问题。告诉使用你的东西的每个人要么遵守你的“规则”,否则程序会搞砸。

    【讨论】:

      【解决方案4】:

      一个没有太多魔法的明确解决方案是维护一个准备回调列表:

      class BaseHandler(object):
          def __init__(self):
              self.prepare_callbacks = []
          def register_prepare_callback(self, callback):
              self.prepare_callbacks.append(callback)
          def prepare(self):
              # Do BaseHandler preparation
              for callback in self.prepare_callbacks:
                  callback()
      
      class MyHandler(BaseHandler):
          def __init__(self):
              BaseHandler.__init__(self)
              self.register_prepare_callback(self._prepare)
          def _prepare(self):
              # whatever
      

      【讨论】:

      • 注意你已经用你的子类覆盖了BaseHandler.__init__,所以你最终会得到一个AttributeError...
      • @poke:谢谢,已修复。 :) 好吧,现在我以某种方式将问题转移到__init__()...
      • 您可以将列表的初始化移动到类主体(即在 __init__ 之前,在任何方法之外),或者您可以在 register 方法中添加一个存在测试,如果必要的。
      • @poke:我喜欢这两个建议,尤其是第一个。我也喜欢你的回答。
      【解决方案5】:

      一般而言,您可以尝试使用__getattribute__ 来实现类似的效果(直到有人也覆盖了此方法),但这与 Python 的想法背道而驰。能够在 Python 中访问私有对象成员是有原因的。原因在import this中有提到

      【讨论】:

        猜你喜欢
        • 2015-07-15
        • 2016-08-02
        • 1970-01-01
        • 2017-08-27
        • 2014-05-11
        • 2017-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多