23
votes

I know python functions are virtual by default. Let's say I have this:

class Foo:
    def __init__(self, args):
        do some stuff
    def goo():
        print "You can overload me"
    def roo():
        print "You cannot overload me"

I don't want them to be able to do this:

class Aoo(Foo):
    def roo():
        print "I don't want you to be able to do this"

Is there a way to prevent users from overloading roo()?

5
Why would you want such a thing? Are you afraid someone will override it and it won't work for them? it's their problem. But sometimes, they know what they are doing and they just need to do it. I've spent three days workaround this kind of limitation in Java, in Python it was 20 seconds. - pupeno
@pupeno Clean coding may require invariants that should not be broken by unintended overrides. In addition, your argument is only true in cases you have no access to the superclass code (such as in a 3rd party library). More lengthy: Think of a complex Python project with more than one developer and you being the designer/code owner of a class that lives from certain invariants that your function implementations ensure. You want to avoid breaks of the invariants in lazy subclass implementations. If someone needs a change, he can do it in the superclass code or initiate a design discussion. - matheburg

5 Answers

41
votes

You can use a metaclass:

class NonOverridable(type):
    def __new__(self, name, bases, dct):
        if bases and "roo" in dct:
            raise SyntaxError, "Overriding roo is not allowed"
        return type.__new__(self, name, bases, dct)

class foo:
    __metaclass__=NonOverridable
    ...

The metatype's new is called whenever a subclass is created; this will cause an error in the case you present. It will accept a definition of roo only if there are no base classes.

You can make the approach more fancy by using annotations to declare which methods are final; you then need to inspect all bases and compute all final methods, to see whether any of them is overridden.

This still doesn't prevent somebody monkey-patching a method into a class after it is defined; you can try to catch these by using a custom dictionary as the classes' dictionary (which might not work in all Python versions, as classes might require the class dictionary to be of the exact dict type).

32
votes

Python 3.8 (released Oct/2019) adds final qualifier to typing.

A final qualifier was added to the typing module---in the form of a final decorator and a Final type annotation---to serve three related purposes:

  • Declaring that a method should not be overridden
  • Declaring that a class should not be subclassed
  • Declaring that a variable or attribute should not be reassigned
from typing import final

class Base:
    @final
    def foo(self) -> None:
        ...

class Derived(Base):
    def foo(self) -> None:  # Error: Cannot override final attribute "foo"
                            # (previously declared in base class "Base")
        ...

It is in line with what your were asking and is supported by core Python now.

Have a look at PEP-591 for more details.

10
votes

Since Python has monkey patching, not only can you not make anything "private". Even if you could, someone could still monkeypatch in a new version of the method function.

You can use this kind of name as a "don't go near" warning.

class Foo( object ):
    def _roo( self ):
       """Change this at your own risk."""

That's the usual approach. Everyone can read your source. They were warned. If they boldly go where they were warned not to go, they get what they deserve. It doesn't work and you can't help them.

You can try to make this intentionally obcure with inner classes and "hidden" implementation modules that are called by the "private" methods. But... everyone has your source. You can't prevent anything. You can only advise people of the consequences of their actions.

6
votes
def non_overridable(f):
    f.non_overridable = True
    return f

class ToughMeta(type):
    def __new__(cls, name, bases, dct):
        non_overridables = get_non_overridables(bases)
        for name in dct:
            if name in non_overridables:
                raise Exception ("You can not override %s, it is non-overridable" % name)
        return type.__new__(cls, name, bases, dct)

def get_non_overridables(bases):
    ret = []
    for source in bases:
        for name, attr in source.__dict__.items():
            if getattr(attr, "non_overridable", False):
                ret.append(name)
        ret.extend(get_non_overridables(source.__bases__))
    return ret

class ToughObject(object):
    __metaclass__ = ToughMeta
    @non_overridable
    def test1():
        pass

# Tests ---------------
class Derived(ToughObject):
    @non_overridable
    def test2(self):
        print "hello"

class Derived2(Derived):
    def test1(self):
        print "derived2"

# --------------------
0
votes

Late to the party but not all python methods are "virtual" by default - consider:

class B(object):
    def __priv(self): print '__priv:', repr(self)

    def call_private(self):
        print self.__class__.__name__
        self.__priv()

class E(B):
    def __priv(self): super(E, self).__priv()

    def call_my_private(self):
        print self.__class__.__name__
        self.__priv()

B().call_private()
E().call_private()
E().call_my_private()

Blows due to name mangling:

B
__priv: <__main__.B object at 0x02050670>
E
__priv: <__main__.E object at 0x02050670>
E
Traceback (most recent call last):
  File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 35, in <module>
    E().call_my_private()
  File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 31, in call_my_private
    self.__priv()
  File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 27, in __priv
    def __priv(self): super(E, self).__priv()
AttributeError: 'super' object has no attribute '_E__priv'

So if you want to get some help from the language to prohibit people from overriding a bit of functionality that you need inside your class this is the way to go. If the method you want to make final is part of your class API however you are stuck with the comments approach (or metaclass hacks). My personal opinion is that a final keyword is very useful for inheritance - as you can avoid the class breaking in insidious ways when overridden (consider using the "final" method in super implementation for instance and then someone overrides - boom, super broken) - and for documentation purposes (no docs are better than a compile time syntax error) - but Python's dynamic nature would not allow it and hacks are fragile - so add a docstring:

"""DON'T OVERRIDE THIS METHOD"""