强制子类在重写时调用父方法

时间:2015-12-11 13:40:54

标签: python class

我很好奇是否有一种方法可以强制(从Parent类)强制父类方法在被覆盖时从子类调用。

示例:

class Parent(object):

    def __init__(self):
        self.someValue=1

    def start(self):
        ''' Force method to be called'''
        self.someValue+=1

我希望我的孩子Child类要做的正确实现是:

class ChildCorrect(Parent):

    def start(self):
        Parent.start(self)
        self.someValue+=1

但是,是否有办法强制开发Child类的开发人员专门调用(而不仅仅是覆盖)' start'在Parent类中定义的方法,以防它们忘记调用它:

class ChildIncorrect(Parent):

    def start(self):
        '''Parent method not called, but correctly overridden'''
        self.someValue+=1

另外,如果这不被认为是最佳做法,那还有什么选择呢?

8 个答案:

答案 0 :(得分:18)

如何(不)这样做

不,没有安全的方法强迫用户拨打超级电话。让我们回顾几个可达到或类似目标的选项,并讨论为什么这是一个坏主意。在下一节中,我还将讨论合理的(关于Python社区)处理这种情况的方法。

  1. 在定义子类时,元类可以检查覆盖目标方法(=与目标方法具有相同名称)的方法是否使用适当的参数调用super。

    这需要深度特定于实现的行为,例如使用CPython的dis模块。在任何情况下我都无法想象这是一个好主意 - 它取决于CPython的特定版本以及您使用CPython的事实。

  2. 元类可以与基类合作。在这种情况下,基类会在调用继承的方法时通知元类,并且元类使用一段保护代码包装任何重写方法。

    保护代码测试在执行覆盖方法期间是否调用了继承方法。这有一个缺点,即每种方法都有一个单独的通知通道,需要这个"功能"是必需的,此外线程安全和重新进入将是一个问题。此外,重写方法已经完成执行,您注意到它没有调用继承方法,这可能是坏的,具体取决于您的方案。

    它还打开了一个问题:如果重写方法没有调用继承方法该怎么办?使用该方法的代码引发异常可能是意料之外的(或者更糟糕的是,它可能会假设该方法完全没有效果,这是不正确的。)

    同样反对开发人员覆盖课程的最新反馈(如果他们得到反馈的那些!)也不好。

  3. 元类可以为每个重写方法生成一段保护代码,该方法在重写方法执行之前或之后自动调用继承的方法

    这里的缺点是开发人员不会期望自动调用继承的方法并且无法停止该调用(例如,如果不满足子类的特殊前提条件)或者在他们自己的重写方法中控制继承的方法被称为。

    这在编写python时违反了一系列明智的原则(避免意外,显式优于隐式,可能更多)。

  4. 第2点和第3点的组合。使用第2点的基本和元类的合作,第3点的保护代码可以扩展为自动调用super iff 重写方法并没有超级自称。

    同样,这是意料之外的,但它解决了重复超级调用以及如何处理不调用super的方法的问题。

    然而,仍然存在问题。虽然线程安全性可以通过线程本地来修复,但是当不满足前置条件时,仍然无法使用重写方法中止对super的调用,除非引发异常,这在所有情况下都是不可取的。此外,super只能在覆盖方法之后自动调用,而不是之前,这在某些情况下是不合需要的。

  5. 此外,这对于在对象和类的生命周期内重新绑定属性没有任何帮助,尽管这可以通过使用描述符和/或扩展元类来处理它来帮助。

    为什么不这样做

      

    另外,如果这不被认为是最佳做法,那还有什么选择呢?

    Python的常见最佳做法是假设您是成年人之一。这意味着,除非您允许,否则没有人会积极尝试对您的代码执行令人讨厌的事情。在这样的生态系统中,在方法或类的文档中添加.. warning::是有意义的,这样任何从该类继承的人都知道他们必须做什么。

    此外,在适当的位置调用super方法在很多情况下都是有意义的,使用基类的开发人员无论如何都会考虑它而只是偶然忘记它。在这种情况下,使用上面第三点的元类无助于用户必须记住来调用super,这可能是一个问题本身,特别是对于有经验的程序员。

    它也违反了最少惊喜的原则,并且"明确优于隐含" (没人希望隐式调用继承的方法!)。再次,这必须记录得很好,在这种情况下你也可以诉诸于没有超级自动调用而只是文档,它使比平时更有意义来调用继承的方法。

答案 1 :(得分:6)

如果类层次结构在您的控制之下,您可以使用四人帮(Gamma等)设计模式书籍调用模板方法模式:

class MyBase:
   def MyMethod(self):
      # place any code that must always be called here...
      print "Base class pre-code"

      # call an internal method that contains the subclass-specific code
      self._DoMyMethod()

      # ...and then any additional code that should always be performed
      # here.
      print "base class post-code!"

   def _DoMyMethod(self):
      print "BASE!"


class MyDerived(MyBase):
   def _DoMyMethod(self):
      print "DERIVED!"


b = MyBase()
d = MyDerived()

b.MyMethod()
d.MyMethod()

输出:

Base class pre-code
BASE!
base class post-code!
Base class pre-code
DERIVED!
base class post-code!

答案 2 :(得分:2)

  

但是,是否有办法强制开发人员开发Child类   专门打电话(不仅仅是覆盖)'开始'方法   父类,以防他们忘记调用它

在Python中,您无法控制其他人如何覆盖您的功能。

答案 3 :(得分:2)

Python中很少有人强迫其他程序员以某种方式编写代码。即使在标准库中,您也会发现警告such as this

  

如果子类重写构造函数,它必须确保在对线程执行任何其他操作之前调用基类构造函数。

虽然可以使用元类来修改子类'行为,在这种情况下它可以可靠地做的最好的方法是用另一种方法替换新方法,这种方法将负责调用被替换的方法和原始方法。但是,如果程序员 在新代码中调用了父方法,那么这会产生问题,加上它会导致不应该调用父方法的坏习惯。

换句话说,Python不是那样构建的。

答案 4 :(得分:2)

对于某些元类黑客攻击,您可以检测在运行时是否已调用父启动。但是我肯定建议在实际情况下使用此代码,它可能在很多方面都有错误,并且强制执行此类限制并不是pythonic。

class ParentMetaClass(type):

    parent_called = False

    def __new__(cls, name, bases, attrs):
        print cls, name, bases, attrs
        original_start = attrs.get('start')
        if original_start:
            if name == 'Parent':
                def patched_start(*args, **kwargs):
                    original_start(*args, **kwargs)
                    cls.parent_called = True

            else:
                def patched_start(*args, **kwargs):
                    original_start(*args, **kwargs)
                    if not cls.parent_called:
                        raise ValueError('Parent start not called')
                    cls.parent_called = False  

            attrs['start'] = patched_start

        return super(ParentMetaClass, cls).__new__(cls, name, bases, attrs)


class Parent(object):
    __metaclass__ = ParentMetaClass

    def start(self):
        print 'Parent start called.'


class GoodChild(Parent):
    def start(self):
        super(GoodChild, self).start()
        print 'I am a good child.'


class BadChild(Parent):
    def start(self):
        print 'I am a bad child, I will raise a ValueError.'

答案 5 :(得分:0)

这与Python并不直接相关,大多数OO语言都是如此。您应该在基类中定义一个抽象方法,使其成为非公共方法(但可用于子类覆盖),并在父类中创建一个公共模板方法,该方法将调用该抽象方法(在子类中定义)。客户端代码将无法直接调用有问题的方法,但将能够调用模板方法,而模板方法又将确保以正确的顺序/上下文调用有问题的方法。

换句话说,您的系统不得依赖于调用父级实现的子类。如果这是必须的,那么您确实应该检查您的实现并分解有问题的方法(例如,使用模板方法模式)。

另外,如果子类的构造函数未明确执行父语言的构造函数,则某些语言(如Java)会隐式调用父类的构造函数(尽管不适用于普通方法)。

无论如何,请不要依赖“ hacks”。那不是解决问题的正确方法。

答案 6 :(得分:0)

我认为@bgporter处在正确的轨道上,但是它缺少语法可执行性,以下模式可以帮助实现:

from abc import abstractmethod

class Parent:

    def __init__(self):
        self.value = 1

    def before_start(self):
        self.value += 1
    
    @abstractmethod
    def on_start(self):
        ...

    def after_start(self):
        pass

    def start(self):
        # Force method to be called
        self.before_start()
        self.on_start()
        self.after_start()


class Child(Parent): 
    """
    mypy, pylance and and pycharm will warn about 
    unimplemented abstract method if on_start is missing.
    Child can now override before_start and after_start behaviors as well. 
    This pattern is often seen in state machines so perhaps that's what you are after. 
    """ 
    def on_start(self):
        self.value += 1


child = Child()
child.start()
print(child.value) # prints 3

答案 7 :(得分:-1)

您所需要的只是super

有了它,您的代码可能如下所示:

class Parent(object):
    def __init__(self):
        self.someValue = 1

    def start(self):
        self.someValue += 1


class Child(Parent):
    def start(self):
        # put code here
        super().start()
        # or here