这是来自" Effective Python"的一个例子,我显然遗漏了一些东西。我添加了一些印刷品以帮助说服自己,但我还是有点不清楚。
据我所知,当您尝试访问继承的私有变量时,它会失败,因为在子实例字典中,名称已被修改(下面的最后一行,尝试访问a.__value
正确地失败,因为实例dict包含受损版本_ApiClass__value
)。
我被绊倒的地方是为什么继承的方法get
没有这个问题。如果您在self.__dict__
的通话中打印get
,您可以看到我们仍在使用与我们尝试直接使用来自孩子的点线访问时相同的子实例字典(仅包含损坏的名称)。此方法中的虚线属性访问以某种方式正确转换为受损名称并检索私有变量。
我对属性访问的理解是,在幕后,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) #fail bc name mangled to '_ApiClass_value'
答案 0 :(得分:1)
名称修改是在字节代码编译时完成的,因此名称修改取决于函数的定义位置,而不是调用它的位置。 Child
没有自己的get
方法,它正在使用ApiClass
,而ApiClass
的{{1}}被修改为与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_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
是一个类的实例,那么由于名称修改保护,这种情况极不可能。