Reputation: 2792
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
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