python私有属性名称混乱继承
这是“高效Python”中的一个例子,我显然遗漏了一些东西。我添加了一些印刷品来帮助说服自己,但我还是有点不清楚 我知道,当您尝试访问继承的私有变量时,它会失败,因为在子对象的实例字典中,名称已损坏(下面最后一行,尝试访问python私有属性名称混乱继承,python,inheritance,private,Python,Inheritance,Private,这是“高效Python”中的一个例子,我显然遗漏了一些东西。我添加了一些印刷品来帮助说服自己,但我还是有点不清楚 我知道,当您尝试访问继承的私有变量时,它会失败,因为在子对象的实例字典中,名称已损坏(下面最后一行,尝试访问a.。\uu值理所当然地会失败,因为实例dict包含损坏的版本\u ApiClass\uu值) 我被绊倒的地方是为什么继承的方法get没有这个问题。如果您在调用get时打印self.\uuuu dict\uuuu,您可以看到我们仍在使用相同的子实例dict,就像我们试图直接从子
a.。\uu值
理所当然地会失败,因为实例dict包含损坏的版本\u ApiClass\uu值
)
我被绊倒的地方是为什么继承的方法get
没有这个问题。如果您在调用get
时打印self.\uuuu dict\uuuu
,您可以看到我们仍在使用相同的子实例dict,就像我们试图直接从子实例使用虚线访问(仅包含损坏的名称)一样。此方法中的虚线属性访问以某种方式正确地转换为损坏的名称并检索私有变量
我对属性访问的理解是,在封面下,本质上发生的事情(尽管是简化的)是a.\uu值
基本上是a.\uu dict\uu[''.\uu值']
。这是有道理的,当您试图直接访问继承的私有变量时,它就被证明了,因为它失败了,因为只有损坏的名称在子dict中。然而,继承的方法get
,它在来自子dict的同一个实例dict上操作,使用点访问,因此它显然没有执行a.。uuu dict_uuuu[''uuu值']
,而是a.uu dict_uu['''u ApiClass_uvalue']
这里的区别是什么,使得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) #fail bc name mangled to '_ApiClass_value'
名称篡改是在字节码编译时完成的,因此名称篡改取决于函数的定义位置,而不是函数的调用方式
Child
没有自己的get
方法,它使用的是ApiClass
,而ApiClass
的get
被损坏,无法使用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\u ATTR
值,\u Parent\uuuuu x
已硬编码到字节码中
您还可以演示普通函数中如何不涉及特殊行为(与定义为类一部分的方法相反):
其中,
加载属性
只是尝试加载普通的\uuux
名称,而不是损坏的版本;如果bar
是一个类的实例,那么由于名称损坏保护,这不太可能起作用。名称损坏是在字节码编译时完成的,因此名称损坏取决于函数的定义位置,而不是它的调用方式Child
没有自己的get
方法,它使用的是ApiClass
,而ApiClass
的get
被损坏,无法使用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\u ATTR
值,\u Parent\uuuuu x
已硬编码到字节码中
您还可以演示普通函数中如何不涉及特殊行为(与定义为类一部分的方法相反):
其中,
加载属性
只是尝试加载普通的\uuux
名称,而不是损坏的版本;如果bar
是一个类的实例,由于名称损坏保护,这很可能不起作用。您的ApiClass.get()
的第二行上的注释表明该行不起作用,但没有问题。除了在最后一行尝试访问a.\uu值
之外,我可以运行此代码,没有任何错误。文档中清楚地解释了损坏规则:@Craig-修复了第二行的打字错误,对此表示抱歉。也就是说,让我参考文档并不是特别有帮助。您的ApiClass.get()
的第二行上的注释表明该行不起作用,但很好。除了在最后一行尝试访问a.\uu值
之外,我可以运行此代码,没有任何错误。文档中清楚地解释了损坏规则:@Craig-修复了第二行的打字错误,对此表示抱歉。也就是说,让我参考这些文件并没有特别大的帮助。