0

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'. 
3
  • The comment on the second line of your ApiClass.get() suggests that the line doesn't work, but it is fine. I can run this code with no errors except on the very last line where you try to access a.__value. Commented Mar 22, 2017 at 2:53
  • The mangling rules are explained clearly in the documentation: docs.python.org/2/tutorial/… Commented Mar 22, 2017 at 2:54
  • 1
    @Craig - Fixed the typo on the second line, sorry about that. That said, referring me to the docs is not particularly helpful. Commented Mar 23, 2017 at 4:35

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.