Search code examples
pythonpython-3.xoverridingsubclassing

How to protect function from base class in Python?


I am currently learning the Template Method pattern in python.

I am wondering if there is any way to protect some of the functions from the base class so that the subclass cannot overwrite? As below, the _primitive_operation_3 from the subclass overwrites the same function from the base class.

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()

And if methods from base class cannot be prevented being overwritten, how can I put something in place to give an automatic alert/warning indicating that a particular method from the base class has been overwritten?


Solution

  • You can't prevent subclasses from using the same names, no. You can protect names against accidental shadowing, however, by giving the name a double underscore prefix:

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

    The Python compiler will replace all references to that name, within methods of a class, to add the classname as a prefix. Here, that's AbstractClass, so the actual name becomes _AbstractClass__primitive_operation_3, but you because the compiler rewrites all references, you transparently keep using __primitive_operation_3 in your code.

    Any __primitive_operation_3 names on a subclass would get renamed with a different prefix, simply because they are defined on a class with a different name.

    This feature is explicitly aimed at base classes that want to allow subclasses to use a wide range of names in their definitions.

    See the Reserved classes of identifiers section in the lexical analysis reference documentation:

    __*

    Class-private names. Names in this category, when used within the context of a class definition, are re-written to use a mangled form to help avoid name clashes between “private” attributes of base and derived classes.

    and the Identifiers section of the expressions documentation:

    Private name mangling: When an identifier that textually occurs in a class definition begins with two or more underscore characters and does not end in two or more underscores, it is considered a private name of that class. Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier __spam occurring in a class named Ham will be transformed to _Ham__spam. This transformation is independent of the syntactical context in which the identifier is used. If the transformed name is extremely long (longer than 255 characters), implementation defined truncation may happen. If the class name consists only of underscores, no transformation is done.

    Subclasses can still override the name, but have to explicitly include the same prefix.

    Note that you can't use this mechanism to avoid special methods (with leading and trailing __ double underscores, e.g. __init__ or __len__) from being overridden in a subclass. Clear project documentation is paramount if subclasses of your base class can't override specific methods without taking care to call the base implementation. At best you can detect if a subclass is overriding a method by checking for missing side effects (which is how the standard library has protected Thread.__init__ overriding or you can check if self.methodname.__func__ is ClassObject.methodname is true still before calling the method.