1

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.

Community
  • 1
  • 1
bossylobster
  • 9,603
  • 1
  • 39
  • 55

1 Answers1

2

Due to Python's name-mangling, the actual name of the attribute as stored on t is _TestClass__obj_val (as you can see in dir(t)). The external function, not having been defined on a class, is just looking for __obj_val.

The mangled name is stored in the function's code object (e.g. t.add_extern.func_code.co_names) and is read-only, so there's no easy way to update it.

So there's a reason not to use that technique... at least with mangled names.

kindall
  • 158,047
  • 31
  • 244
  • 289
  • I'm 100% aware of all of this. I am curious if this is a feature or a bug. The action of binding the function to the instance is in some ways an incomplete bind without it, so IMO it's a bug. It really is a corner case though, so it's no big deal. – bossylobster Jun 06 '12 at 01:46
  • Oh, well, in *that* case. Yeah, looks like a bug, but I don't think creating new instance methods is documented anyway. Clearly the name-mangling stuff is in the function constructor instead of in the method constructor where it belongs. – kindall Jun 06 '12 at 03:13