25

Is there any way I can prevent a child class from overriding a method in the base class?

My guess is that there is not, but I'm coming from the .NET world, and I'm trying to make my API as robust as possible, so any input is greatly appreciated.

class Parent:
    def do_something(self):
        '''This is where some seriously important stuff goes on'''
        pass

class Child(Parent):
    def do_something(self):
        '''This should not be allowed.'''
        pass

Is it possible to enforce this? I know the compiler won't help, so maybe by means of some runtime check? Or is it just not a pythonic way of going about things?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Klaus Byskov Pedersen
  • 117,245
  • 29
  • 183
  • 222
  • 3
    In short - not really in python. http://stackoverflow.com/questions/2425656/how-to-prevent-a-function-from-being-overridden-in-python – wkl Oct 16 '10 at 12:28
  • 3
    Btw, this made me think of, re-read and crack up on http://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5000-open-source-java.html - thanks! –  Oct 16 '10 at 12:47
  • @delnan thanks for the link. Good stuff! – Klaus Byskov Pedersen Oct 16 '10 at 12:55
  • 2
    I disagree with the accepted answer. I don't believe python is in the business of branding "forbidden jutsus". I've always thought python's structure is to *allow* paradigms used in many other languages in a more readable format. [Here's a link proving just that](http://stackoverflow.com/a/321240/1658908). – Noob Saibot Jul 21 '14 at 18:32

3 Answers3

21

You are right: what you are attempting is contrary to Python's structure and its culture.

Document your API, and educate your users how to use it. It's their program, so if they still want to override your function, who are you to prevent them?

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • 21
    This is a principal of good program design for a complex system, language conventions aside. Example: You have a module framework that exposes an interface for all modules subclassing it called "run()". In the superclass, run() does a few internal pre/post-processing steps common to all modules (setting a self.hasRun flag, for example) and runs self.runBody(). Inside the subclasses, the actual body of code to be run is inside the runBody() method. To enforce safe module design, I want to prevent run() from being overridden. Solve my problem in a Pythonic way :) – Will Jul 08 '13 at 01:08
  • 3
    (In Java, I'd just declare run() as final in the superclass, and declare runBody as abstract.) – Will Jul 08 '13 at 01:09
  • 1
    @Will In the docstring for `run`, write: "Don't override this, override runBody". – Cai Apr 21 '20 at 09:42
20

If a API lets you provide subclasses of a certain class and calls your (legally) overridden methods, but also other API methods of that class with simple names like "add", accidentally overriding those methods could lead to hard-to-track-down bugs. It's better to at least warn the user.

The cases where a user wants/needs to override a method that will completely break the API is practically zero. The cases where a user accidentally overrides something that he shouldn't and needs hours to find the culprit are far more frequent. Debugging faulty behaviour caused by this can be cumbersome.

This is how I use to warn or protect attributes from being accidentally overridden:

def protect(*protected):
    """Returns a metaclass that protects all attributes given as strings"""
    class Protect(type):
        has_base = False
        def __new__(meta, name, bases, attrs):
            if meta.has_base:
                for attribute in attrs:
                    if attribute in protected:
                        raise AttributeError('Overriding of attribute "%s" not allowed.'%attribute)
            meta.has_base = True
            klass = super().__new__(meta, name, bases, attrs)
            return klass
    return Protect

You can use it like this:

class Parent(metaclass=protect("do_something", "do_something_else")):
    def do_something(self):
        '''This is where some seriously important stuff goes on'''
        pass

class Child(Parent):
    def do_something(self):
        '''This will raise an error during class creation.'''
        pass
uzumaki
  • 1,743
  • 17
  • 32
10

uzumaki already provided one metaclass as a possible solution to the question asked above, but here is another with example usage. Following an attempt to create a Child class, another way of making it difficult to override methods is shown. Putting two underscores before but not after an attribute name will automatically cause name mangling to be invoked. See this answer to another question for an easy-to-use way of accessing this ability manually.

#! /usr/bin/env python3
class Access(type):

    __SENTINEL = object()

    def __new__(mcs, name, bases, class_dict):
        private = {key
                   for base in bases
                   for key, value in vars(base).items()
                   if callable(value) and mcs.__is_final(value)}
        if any(key in private for key in class_dict):
            raise RuntimeError('certain methods may not be overridden')
        return super().__new__(mcs, name, bases, class_dict)

    @classmethod
    def __is_final(mcs, method):
        try:
            return method.__final is mcs.__SENTINEL
        except AttributeError:
            return False

    @classmethod
    def final(mcs, method):
        method.__final = mcs.__SENTINEL
        return method


class Parent(metaclass=Access):

    @Access.final
    def do_something(self):
        """This is where some seriously important stuff goes on."""
        pass


try:
    class Child(Parent):

        def do_something(self):
            """This should not be allowed."""
            pass
except RuntimeError:
    print('Child cannot be created.')


class AnotherParent:

    def __do_something(self):
        print('Some seriously important stuff is going on.')

    def do_parent_thing(self):
        self.__do_something()


class AnotherChild(AnotherParent):

    def __do_something(self):
        print('This is allowed.')

    def do_child_thing(self):
        self.__do_something()


example = AnotherChild()
example.do_parent_thing()
example.do_child_thing()
Community
  • 1
  • 1
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117