Vladimir Vislovich
Vladimir Vislovich

Reputation: 381

Python name mangling allows access both ways

So I have come across a very interesting behavior of python name mangling. Consider the following code

class C:
    def __init__(self):
        self.__c = 1
    
    @staticmethod
    def change(instance):
        print(dir(instance))
        print(instance.__c)
        print(instance._C__c)

Here I create a private field __c and expect to have direct access to it from within class and access via _C__c from outside of the class. So, if we pass an instance of C to C.change either 2nd or 3rd print should fail.
Lets check:


>>> c =  C()
>>> dir(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
>>> C.change(c)
['_C__c', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'change']
1
1

First, for debug we print all available members of c with dir(c). Then we call C.change passing it the variable c.
Hmm, that is unexpected, no errors.
So, first print in change shows us all the available entries of the instance object. Here we see that field __c is available as _C__c. That seems ok, since we access not through self, but through another variable.
Having such output from 'dir' I expect print(instance.__c) to fail with AttributeError.
However, unexpectedly, it works just fine!
This really confuses me, since I do not understand, why is __c accessible and if it is so by design, then why is it not listed in dir output?

Upvotes: 3

Views: 220

Answers (2)

a_guest
a_guest

Reputation: 36249

Whenever you write __c inside a class, it will be textually replaced by _<classname>__c. It's not dynamically performed, it's done at the parsing stage. Hence, the interpreter won't ever see __c, only _<classname>__c. That's why only _C__c appears in dir(instance).

Quoting the docs:

[...] Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier __spam occurring in a class named Ham will be transformed to _Ham__spam. This transformation is independent of the syntactical context in which the identifier is used. [...]

For that reason, it only applies to dotted attribute access (x.y), not to dynamic access via (get|set)attr:

>>> class Foo:
...     def __init__(self):
...         setattr(self, '__x', 'test')
... 
>>> Foo().__x
'test'

Upvotes: 2

chepner
chepner

Reputation: 531175

__-prefixed names work fine as-is inside the class's own methods. It's only outside the class (including in its subclasses) that the modified name is needed to access the attribute, due to name-mangling.

Upvotes: 0

Related Questions