SHORT VERSION: External methods bound to an instance can't access private variables directly via self.__privatevarname. Is this a feature or a bug?
EXTENDED VERSION (WITH EXPLANATION AND EXAMPLE):
In Python: Bind an Unbound Method?, Alex Martelli explains a simple method for binding a function to an instance.
Using this method, one can use external functions to set instance methods in a class (in __init__).
However, this breaks down when the function being bound to the instance needs to access private variables. This is because the name mangling occurs during the the compile step in _Py_Mangle, and so the function never has a chance to call __getattribute__('_classname__privatevarname').
For example, if we define a simple external addition function that accesses the private instance variable __obj_val:
def add_extern(self, value):
return self.__obj_val + value
and bind it to each instance in __init__ while also defining a similar instance method add_intern in the class definition
class TestClass(object):
def __init__(self, object_value):
self.__obj_val = object_value
self.add_extern = add_extern.__get__(self, TestClass)
def add_intern(self, value):
return self.__obj_val + value
then the internal method will work but the external bound method will raise an exception:
>>> t = TestClass(0)
>>> t.add_intern(1)
1
>>> t.add_extern(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in add_extern
AttributeError: 'TestClass' object has no attribute '__obj_val'
POSTSCRIPT: You can overcome this shortcoming by overriding __getattribute__ to do the mangling for you:
class TestClass(object):
...
def __getattribute__(self, name):
try:
return super(TestClass, self).__getattribute__(name)
except AttributeError:
# mimic behavior of _Py_Mangle
if not name.startswith('__'): # only private attrs
raise
if name.endswith('__'): # don't mangle dunder attrs
raise
if '.' in name: # don't mangle for modules
raise
if not name.lstrip('_'): # don't mangle if just underscores
raise
mangled_name = '_{cls_name}{attr_name}'.format(
cls_name=self.__class__.__name__, attr_name=name)
return super(TestClass, self).__getattribute__(mangled_name)
However, this doesn't leave the variables private to external callers, which we don't want.