【问题标题】:Private attribute name mangling inheritance私有属性名称重整继承
【发布时间】:2017-08-13 23:22:33
【问题描述】:

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

我知道当您尝试访问继承的私有变量时,它会失败,因为在子实例字典中,名称已被破坏(下面的最后一行,尝试访问 a.__value 正确失败,因为实例字典包含修改后的版本_ApiClass__value)。

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

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

【问题讨论】:

  • ApiClass.get()第二行的评论表明该行不起作用,但没关系。除了在您尝试访问a.__value 的最后一行之外,我可以毫无错误地运行此代码。
  • mangling 规则在文档中解释得很清楚:docs.python.org/2/tutorial/…
  • @Craig - 修正了第二行的错字,对此感到抱歉。也就是说,将我推荐给文档并不是特别有用。

标签: python inheritance private


【解决方案1】:

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

这是故意的。这里的目标是在类X 中为X 定义的方法,无论您如何访问它们。如果他们没有,并且父母和孩子都定义了一个具有相同名称的私有变量,则父母将无法私有访问其自己的唯一版本的变量,它将与孩子共享(即使在每种情况下,变量的含义可能完全不同)。

dis 模块可以证明修改是在编译时进行的:

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 是硬编码到字节码中的。

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

>>> 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 是一个类的实例,由于名称修改保护,这不太可能起作用。

【讨论】:

    猜你喜欢
    • 2013-09-04
    • 2011-08-03
    • 2023-03-09
    • 2016-08-06
    • 1970-01-01
    • 1970-01-01
    • 2012-04-16
    • 2012-01-05
    • 1970-01-01
    相关资源
    最近更新 更多