这是“Effective Python”中的一个例子,我显然遗漏了一些东西。我添加了一些印刷品以帮助说服自己,但我仍然有点不清楚。
我知道当您尝试访问继承的私有变量时,它会失败,因为在子项的实例字典中,名称已被损坏(下面的最后一行,尝试正确访问a.__value
失败,因为实例 dict 包含损坏的版本_ApiClass__value
)。
我被绊倒的地方是为什么继承的方法get
没有这个问题。如果您self.__dict__
在对 的调用中打印get
,您可以看到我们仍在使用相同的 Child 实例字典,就像我们尝试直接从子对象(仅包含损坏的名称)使用点访问一样。此方法中的点属性访问以某种方式正确转换为损坏的名称并检索私有变量。
我对属性访问的理解是,在幕后,本质上发生的事情(尽管简化了)a.__value
基本上是a.__dict__['__value']
. 这是有道理的,并在您尝试直接访问继承的私有变量时得到了证明,因为它失败了,因为在Child dict中只有错误的名称。然而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) #fail bc name mangled to '_ApiClass_value'
名称修改是在字节码编译时完成的,因此名称修改取决于函数的定义位置,而不是通过什么调用它。Child
没有自己的get
方法,它使用ApiClass
's,并且ApiClass
'sget
被破坏以与ApiClass
.
这是故意的。这里的目标是在类X
mangle 中定义的方法,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
是一个类的实例,由于名称修改保护,这极不可能起作用。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句