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

Spe*_*tor 23 python class

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

例:

class Parent(object):

    def __init__(self):
        self.someValue=1

    def start(self):
        ''' Force method to be called'''
        self.someValue+=1
Run Code Online (Sandbox Code Playgroud)

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

class ChildCorrect(Parent):

    def start(self):
        Parent.start(self)
        self.someValue+=1
Run Code Online (Sandbox Code Playgroud)

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

class ChildIncorrect(Parent):

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

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

Jon*_*fer 20

如何(不)这样做

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

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

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

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

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

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

    同样反对开发人员覆盖课程的最新反馈(如果他们得到反馈的话!)是不好的.

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

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

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

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

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

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

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

为什么不这样做

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

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

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

它也违反了最少意外的原则,"显式优于隐式"(没有人希望隐式调用继承的方法!).再次,这必须记录得很好,在这种情况下你也可以诉诸于没有超级自动调用,只是记录它比通常调用继承方法更有意义.


nur*_*tin 14

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

from abc import abstractmethod
from typing import final

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

    @final
    def start(self):
        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, but they cannot override the start method. 
    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
Run Code Online (Sandbox Code Playgroud)


bgp*_*ter 9

如果类层次结构在您的控制之下,您可以使用Gang of Four(Gamma等)Design Patterns一书调用模板方法模式:

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()
Run Code Online (Sandbox Code Playgroud)

输出:

Base class pre-code
BASE!
base class post-code!
Base class pre-code
DERIVED!
base class post-code!
Run Code Online (Sandbox Code Playgroud)

  • @JonasWielicki:嘿.是的,我认为这里的内容是"文件!文件!文件!" 和"阅读文档!" :) (3认同)