Solaxun
Solaxun

Reputation: 2792

Private attribute name mangling inheritance

This is an example from Effective Pʏᴛʜᴏɴ, that I am clearly missing something on. I added a few print's to help convince myself, but I'm still a little unclear.

I understand that when you attempt to access an inherited private variable, it fails because in the instance dictionary of the child, the name has been mangled (last line below, attempting to access a.__value rightfully fails because the instance dict contains the mangled version _ApiClass__value).

Where I'm getting tripped up is why the inherited method get doesn't have this issue. If you print self.__dict__ in the call to get, you can see that we're still working with the same Child instance dict as we would be if we attempted to use dotted access directly from the child (which contains the mangled name only). Dotted attribute access from within this method somehow properly translates to the mangled name and retrieves the private variable.

My understanding of attribute access is that under the covers, what is essentially happening (albeit simplified) is a.__value is basically a.__dict__['__value']. This makes sense and is proved out when you attempt to directly access the inherited private variable, as it fails since only the mangled name is in the Child dict. However the inherited method get, which is operating on the same instance dict from Child, works with dotted access so it is clearly not doing a.__dict__['__value'], but rather a.__dict__['_ApiClass__value'].

What is the distinction here that causes the private attribute access from within the get method to be aware of the mangled name as opposed to similar attribute access from the child?

class ApiClass():
    def __init__(self):
        self.__value = 5

    def get(self):
        print(self.__dict__['_ApiClass__value']) #succeeds
        print(self.__dict['__value']) #fails bc name mangle
        return self.__value # How is this translated to '_ApiClass_value'
                            # but a similar instance lookup fails?

class Child(ApiClass):
    def __init__(self):
        super().__init__()
        self._value = 'hello'

a = Child()
print(a.__dict__)
print(a.get())   # Works, but instance dict has no '__value' key? 
print(a.__value) # Fails because name mangled to '_ApiClass_value'.

Upvotes: 0

Views: 2259

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155363

Name mangling is done at byte code compilation time, so the name mangling depends on where the function was defined, not what it was called through. Child doesn't have its own get method, it's using ApiClass's, and ApiClass's get was mangled to work with ApiClass.

This is intentional. The goal here is that methods defined in class X mangle for X no matter how you reach them. If they didn't, and a parent and child both defined a private variable with the same name, the parent wouldn't have private access to its own unique version of the variable, it would be sharing it with the child (even though the meaning of the variable might be completely different in each case).

The dis module can demonstrate the fact that the mangling is at compile time:

class Parent:
    def x(self):
        return self.__x

class Child(Parent):
    pass

Then interactively inspecting:

>>> import dis
>>> dis.dis(Parent.x)
  3           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (_Parent__x)
              6 RETURN_VALUE
>>> dis.dis(Child.x):
  3           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (_Parent__x)
              6 RETURN_VALUE

Note that the LOAD_ATTR value, _Parent__x is hardcoded into the byte code.

You can also demonstrate how no special behaviors are involved in plain functions (as opposed to methods defined as part of a class):

>>> def foo(bar): return bar.__x
>>> dis.dis(foo)
  1           0 LOAD_FAST                0 (bar)
              3 LOAD_ATTR                0 (__x)
              6 RETURN_VALUE

where the LOAD_ATTR is just trying to load the plain __x name, not a mangled version; if bar was an instance of a class, it's highly unlikely this would work thanks to the name mangling protections.

Upvotes: 3

Related Questions