私有属性名称重整继承

Private attribute name mangling inheritance

这是来自 Effective Pʏᴛʜᴏɴ 的示例,我显然遗漏了一些内容。我添加了一些印刷品来帮助说服自己,但我还是有点不清楚。

我知道当你试图访问一个继承的私有变量时,它失败了,因为在子实例字典中,名字被破坏了(下面最后一行,尝试访问 a.__value 理所当然地失败了,因为实例字典包含损坏的版本 _ApiClass__value).

我被绊倒的地方是为什么继承方法 get 没有这个问题。如果您在 get 的调用中打印 self.__dict__,您可以看到我们仍在使用相同的 Child 实例字典,就像我们尝试直接从子实例(它仅包含损坏的名称)。此方法中的点属性访问以某种方式正确地转换为损坏的名称并检索私有变量。

我对属性访问的理解是,在幕后,本质上发生的(尽管经过简化)是 a.__value 基本上是 a.__dict__['__value']。这是有道理的,当您尝试直接访问继承的私有变量时证明了这一点,因为它失败了,因为只有损坏的名称在 Child dict 中。然而,继承的方法 get 对来自 Child 的同一个实例字典进行操作,它使用点访问,所以它显然不是在做 a.__dict__['__value'],而是 a.__dict__['_ApiClass__value'].

这里的区别是什么导致从 get 方法中进行的私有属性访问意识到损坏的名称,而不是从子方法中进行类似的属性访问?

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

名称修饰是在字节码编译时完成的,因此名称修饰取决于函数定义的位置,而不是调用它的方式。 Child 没有自己的 get 方法,它使用 ApiClass 的方法,而 ApiClassget 被破坏以与 [=15= 一起使用].

这是故意的。这里的目标是 class X 中定义的方法会破坏 X,无论您如何访问它们。如果他们不这样做,并且 parent 和 child 都定义了一个具有相同名称的私有变量,那么 parent 将无法私有访问它自己的唯一版本的变量,它将与 child 共享它(即使变量的含义在每种情况下可能完全不同)。

dis 模块可以证明 mangling 在编译时的事实:

class Parent:
    def x(self):
        return self.__x

class Child(Parent):
    pass

然后交互式检查:

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

请注意,LOAD_ATTR_Parent__x 已硬编码到字节码中。

您还可以演示如何在普通函数中不涉及任何特殊行为(与定义为 class 的一部分的方法相反):

>>> def foo(bar): return bar.__x
>>> dis.dis(foo)
  1           0 LOAD_FAST                0 (bar)
              3 LOAD_ATTR                0 (__x)
              6 RETURN_VALUE

其中 LOAD_ATTR 只是试图加载纯 __x 名称,而不是损坏的版本;如果 bar 是 class 的一个实例,由于名称修改保护,这不太可能起作用。