简体   繁体   中英

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?

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 ( xy ), not to dynamic access via (get|set)attr :

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

__ -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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM