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.