Python 名称修改允许双向访问

Python name mangling allows access both ways

所以我遇到了一个非常有趣的行为 python name mangling。 考虑以下代码

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

这里我创建了一个私有字段 __c 并期望从 class 内部直接访问它,并从 class 外部通过 _C__c 访问它。因此,如果我们将 C 的实例传递给 C.change,那么第二次或第三次打印应该会失败。
让我们检查一下:


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

首先,为了调试,我们使用 dir(c) 打印所有可用的 c 成员。然后我们调用 C.change 将变量 c.
传递给它 嗯,出乎意料,没有错误。
因此,change 中的第一个打印向我们展示了 instance 对象的所有可用条目。在这里我们看到字段 __c 可用作 _C__c。这似乎没问题,因为我们不是通过自身访问,而是通过另一个变量访问。
从 'dir' 获得这样的输出,我预计 print(instance.__c) 会因 AttributeError 而失败。
然而没想到,居然还好好的!
这真的让我感到困惑,因为我不明白,为什么 __c 可以访问,如果它是设计的,那么为什么它没有在 dir 输出中列出?

__-前缀名称在 as-is class 自己的方法中工作正常。由于 name-mangling,只有 class 之外(包括在其子 class 中)需要修改名称才能访问该属性。

每当您在 class 中写入 __c 时,它将被文本替换为 _<classname>__c。它不是动态执行的,它是在解析阶段完成的。因此,解释器永远不会看到 __c,只会看到 _<classname>__c。这就是为什么只有 _C__c 出现在 dir(instance).

中的原因

引用 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. [...]

因此,它仅适用于点属性访问 (x.y),不适用于通过 (get|set)attr:

的动态访问
>>> class Foo:
...     def __init__(self):
...         setattr(self, '__x', 'test')
... 
>>> Foo().__x
'test'