【问题标题】:How to protect function from base class in Python?如何在 Python 中保护函数免受基类的影响?
【发布时间】:2019-04-15 18:14:32
【问题描述】:

我目前learningpython中的模板方法模式。

我想知道是否有任何方法可以保护基类中的某些函数以使子类无法覆盖?如下,子类中的_primitive_operation_3 覆盖了基类中的相同函数。

import abc

class AbstractClass(metaclass=abc.ABCMeta):
    """
    Define abstract primitive operations that concrete subclasses define
    to implement steps of an algorithm.
    Implement a template method defining the skeleton of an algorithm.
    The template method calls primitive operations as well as operations
    defined in AbstractClass or those of other objects.
    """
    def template_method(self):
        self._primitive_operation_1()
        self._primitive_operation_2()
        self._primitive_operation_3()

    # Functions must be specified by subclass (i.e., subclass variant)
    @abc.abstractmethod
    def _primitive_operation_1(self):
        pass

    # Functions must be specified by subclass (i.e., subclass variant)
    @abc.abstractmethod
    def _primitive_operation_2(self):
        pass

    # Functions inherited and not modified by subclass (i.e., subclass invariant)
    def _primitive_operation_3(self):
        print ('Execute operation #3 from main class')

class ConcreteClass(AbstractClass):
    """
    Implement the primitive operations to carry out
    subclass-specificsteps of the algorithm.
    """
    def _primitive_operation_1(self):
        pass

    def _primitive_operation_2(self):
        pass

    # You can still overwrite it if you want
    def _primitive_operation_3(self):
        print ('Execute operation #3 from subclass')

def main():
    concrete_class = ConcreteClass()
    concrete_class.template_method()

if __name__ == "__main__":
    main()

如果无法防止基类中的方法被覆盖,我该如何放置一些东西来发出自动警报/警告,指示基类中的特定方法已被覆盖?

【问题讨论】:

  • 为什么会有问题?重写父方法是子类化的point
  • 我的理解是模板方法模式,有些方法是由子类指定的(这将是抽象方法),而其他方法将保持不变,我想保持不变的方法受保护,因此子类不会意外更改它。
  • @jonrsharpe:并非总是如此。许多框架基类实现需要某些东西才能在没有被覆盖的情况下工作。 Python 甚至有专门的机制来规避这个问题……

标签: python python-3.x overriding subclassing


【解决方案1】:

您不能阻止子类使用相同的名称,不。但是,您可以保护名称免受意外阴影的影响,方法是为名称添加双下划线前缀:

def __primitive_operation_3(self):
    print('Execute operation #3 from main class')

Python 编译器将替换类的方法中对该名称的所有 引用,以添加类名作为前缀。这里是AbstractClass,所以实际名称变成了_AbstractClass__primitive_operation_3,但是你因为编译器重写了所有引用,所以你透明地在代码中继续使用__primitive_operation_3

子类上的任何 __primitive_operation_3 名称都将使用不同的前缀重命名,这仅仅是因为它们是在具有不同名称的类上定义的。

此功能明确针对希望允许子类在其定义中使用广泛名称的基类。

参见词法分析参考文档中的Reserved classes of identifiers section

__*

类私有名称。此类别中的名称在类定义的上下文中使用时,会被重写以使用重整形式,以帮助避免基类和派生类的“私有”属性之间的名称冲突。

以及表达式文档的Identifiers section

私有名称修改:当以文本形式出现在类定义中的标识符以两个或多个下划线字符开头并且不以两个或多个下划线结尾时,它被视为该类的私有名称.在为私有名称生成代码之前,私有名称会转换为更长的形式。转换插入类名,删除前导下划线并在名称前面插入一个下划线。例如,出现在名为Ham 的类中的标识符__spam 将转换为_Ham__spam。这种转换与使用标识符的语法上下文无关。如果转换后的名称非常长(超过 255 个字符),则可能会发生实现定义的截断。如果类名仅包含下划线,则不进行任何转换。

子类仍然可以覆盖名称,但必须明确包含相同的前缀。

请注意,您不能使用此机制来避免特殊方法(带有前导和尾随__ 双下划线,例如__init____len__)在子类中被覆盖。如果您的基类的子类在不注意调用基实现的情况下无法覆盖特定方法,则清晰的项目文档至关重要。充其量您可以通过检查缺少的副作用来检测子类是否覆盖了方法(这就是标准库的protected Thread.__init__ overriding,或者您可以在调用方法之前检查self.methodname.__func__ is ClassObject.methodname 是否仍然为真。

【讨论】:

  • 感谢Martijin,您解释的双下划线与我从M Anaya 的Clean Code in Python 中读到的内容相符。它并没有真正将其设为私有,并且可能会出现问题。我想知道是否没有真正的方法可以防止它被覆盖,有没有办法让它知道并警告基类中的特定方法已被覆盖(参见上面的编辑)?谢谢。
  • @KubiK888:你不能。充其量你可以检查没有发生的副作用。这就是Thread.__init__ is protected
  • @KubiK888: 你也可以检查self.methodname.__func__ is ClassObj.methodname 是否为真,如果不是,那么你有一个被覆盖的方法。
  • @KubiK888:不,__name 约定将名称 class 设为私有。这与许多具有 C++ 或 Java 或 C# 经验的开发人员理解 private 的含义不同。 Python 中的 Clean Code 警告您不应该使用 __name,如果您希望它根据不同的隐私模型工作,但您这里有此约定的确切用例。这是适合您情况的正确方法。
  • 非常感谢!
猜你喜欢
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-06
  • 2015-10-26
  • 2011-07-09
  • 1970-01-01
  • 2014-04-01
相关资源
最近更新 更多