Thursday, March 3, 2011

Making functions non override-able

I know python functions are virtual by default. Lets 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 dont want them to be able to do this:

class aoo(foo):
 def roo():
   print "I dont want you to be able to do this"

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

From stackoverflow
  • 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.

  • 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).

    S.Lott : +1 for a GOOD example of metaclass __new__
    hop : i would hate you, if i had to use your class
    fuzzyman : Couldn't you provide your method as a descriptor that prevented itself from being removed or overriden? Presumably descriptors are only triggered on attribute lookup and not if it they are looked up directly in the class dictionary.
  • 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 comments:

Post a Comment