Friday, July 19, 2013

python and double underscore

As we all know, python itself does not really have  inheritance protections. A child can safely call and/or overwrite the parents' methods/attributes/etc freely. There is no compile time check to make sure the child does not call anything "private" from its parent; or if the parents are trying to access child's methods or properties.

This is by design of the "duck-typing" philosophy, adapted by python: "Give it a shot, if it works, great! If it does not work, well...exception will handle that" (more on this duck-typing another time). But, by convention, python does provide a fake level of protection for private methods. Give this code a run and try to understand:

class Foo(object):
    def __init__(self):
        super(Foo, self).__init__()
        self._one = "this is from Foo"
        self.__two = "this is from Foo too"

    def _getone(self):
        print self._one

    def __gettwo(self):
        print self.__two

    def gettwo(self):
        self.__gettwo()

if __name__ == '__main__':
    f = Foo()
    f._getone()
    try:
        f.__gettwo()
    except:
        traceback.print_exc()
    f.gettwo()


f._getone() will call _getone() method, passing the first argument as f -- an instance of Foo object (the self argument). Everything works as expected, we saw the string "this is from Foo"

f.__gettwo()....raise an exception. There is no __gettwo() method. What the heck? We just defined it!

f.gettwo() will call self.__gettwo() internally, and this time, it works. Why?

Look look: When you dic(f), here is what we get:
['_Foo__gettwo', '_Foo__two', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_getone', '_one', 'gettwo']


Foo class does not have a __gettwo() method, but a _Foo__gettwo() instead! YAY, now we know why there is not a __gettwo() method. Python, by convention, mingle the class name with the method name IF the name starts with double underscore. If you try to call self.__method_name(), Python is smart enough to replace __method_name with _Classname__method_name(), and works as expected. Any external calls to __method_name() will fail since the Class itself does not provide such a method. Same thing for __instance. This provides a "fake" protection from direct manipulation externally.

This also affects inheritance. Since __method_name never exists, you can not try to overwrite with a sub class without using its parents' name. Same goes for attributes:

class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self._one = "this is from Bar"
        self.__two = "this is from Bar too"

    def _getone(self):
        print "Bar:", self._one

    def __gettwo(self):
        print "Bar:", self.__two
if __name__ == '__main__':
    b = Bar()
    b._getone()
    try:
        b.__gettwo()
    except:
        traceback.print_exc()
    b.gettwo()

b._getone() will work as expected, while b.gettwo() will print _Foo__two instead.

This cost me an hour of debug time. Hopefully you don't have to do the same.

No comments:

Post a Comment