Python:从内部类访问外部私有函数
Posted
技术标签:
【中文标题】Python:从内部类访问外部私有函数【英文标题】:Python: accessing outer private functions from inner class 【发布时间】:2016-08-04 16:52:18 【问题描述】:是我遗漏了什么,还是这样的事情不可能?
class Outer:
def __init__(self, val):
self.__val = val
def __getVal(self):
return self.__val
def getInner(self):
return self.Inner(self)
class Inner:
def __init__(self, outer):
self.__outer = outer
def getVal(self):
return self.__outer.__getVal()
foo = Outer('foo')
inner = foo.getInner()
val = inner.getVal()
print val
我收到此错误消息:
return self.__outer.__getVal()
AttributeError: Outer instance has no attribute '_Inner__getVal'
【问题讨论】:
跟我重复一遍:Python 不是 Java。除非您想避免子类发生冲突,否则不要使用类私有名称。没有像 Python 中那样的方法隐私概念。你不需要使用内部类来获得特权访问。 也就是说,不要使用内部类,使用 single 下划线表示不被外部用户使用的 API 内部方法。 有可能,但不是微不足道的,请在***.com/questions/2024566/…查看答案 @JoãoPinto:不同的问题,尽管也源于使用嵌套类。 OP 通过传入对外部类实例的引用已经避免了这个问题。 【参考方案1】:您正在尝试将 Java 技术应用于 Python 类。别。 Python 没有像 Java 那样的隐私模型。类及其实例的所有属性总是可访问,即使在类中使用__name
双下划线名称(它们只是被重命名以添加命名空间)。
因此,您也不需要内部类,因为这样的类没有特权访问。您可以将该类放在 Outer
之外,并拥有完全相同的访问级别。
您遇到错误是因为 Python 重命名 属性在类上下文中具有初始双下划线名称,以避免与子类发生冲突。这些被称为 class private 因为重命名会将类名添加为命名空间;这适用于它们的定义和使用。请参阅参考文档的Reserved classes of identifiers section:
__*
类私有名称。此类别中的名称在类定义的上下文中使用时,会被重写以使用重整形式,以帮助避免基类和派生类的“私有”属性之间的名称冲突。
Outer
中所有带有双下划线的名称都重命名为前缀为 _Outer
,因此 __getVal
重命名为 _Outer__getVal
。 Inner
中的任何此类名称都会发生同样的情况,因此您的 Inner.getVal()
方法将寻找 _Inner__getVal
属性。由于Outer
没有_Inner__getVal
属性,因此您会收到错误消息。
您可以手动将相同的转换应用于Inner.getVal()
以“修复”此错误:
def getVal(self):
return self.__outer._Outer__getVal()
但是您并没有按预期使用双下划线名称,因此请改用单下划线,并且不要使用嵌套类:
class Outer:
def __init__(self, val):
self._val = val
def _getVal(self):
return self._val
def getInner(self):
return _Inner(self)
class _Inner:
def __init__(self, outer):
self._outer = outer
def getVal(self):
return self._outer._getVal()
我将 Inner
重命名为 _Inner
以记录类型是内部实现细节。
虽然我们正在讨论这个主题,但实际上也没有必要使用访问器。在 Python 中,您可以随时在property
objects 和普通属性之间切换。无需像在 Java 中那样进行防御性编码,在属性和访问器之间切换会带来巨大的切换成本。在 Python 中,不要使用 obj.getAttribute()
和 obj.setAttribute(val)
方法。只需使用obj.attribute
和obj.attribute = val
,如果您需要做更多工作来生成或设置值,请使用property
。在您的开发周期中随意切换到或离开property
对象。
因此,您可以将上述进一步简化为:
class Outer(object):
def __init__(self, val):
self._val = val
@property
def inner(self):
return _Inner(self)
class _Inner(object):
def __init__(self, outer):
self._outer = outer
@property
def val(self):
return self._outer._val
这里outer.inner
根据需要生成一个新的_Inner()
实例,并且Inner.val
属性代理存储的self._outer
引用。实例的用户永远不需要知道任一属性是由 property
对象处理的:
>>> outer = Outer(42)
>>> print outer.inner.val
42
请注意,要让property
在 Python 2 中正常工作,您必须使用新样式类;从object
继承来执行此操作;在 property
上的旧式类上支持吸气剂(意味着设置也不会被阻止!)。这是 Python 3 中的默认设置。
【讨论】:
这是一个非常好的答案! – 但我不明白你怎么能得出结论,我没有像我的简单示例那样使用双下划线名称?我用一个新示例和更多信息编辑了我的初始帖子。 @amkleist:同样,Python 不会阻止用户不管。如果您使用__val
,用户仍然可以更改Outer._Outer__val
。 Python 是一种供成年人同意的语言;作为开发人员,您只能记录变量是内部的。您已经通过在名称中使用单个前导下划线来做到这一点。
@amkleist:您可以使用@property
来创建没有设置器的属性。 outer.inner.val
已经是这种情况,尝试使用 outer.inner.val = 'new value'
设置该属性将失败,因为没有定义 setter。这就是设计合理的 Python API 所需要的一切。
@amkleist: 如果一个确定的用户然后仍然去设置outer._val
属性(例如使用outer._val = 'foo'
)那是他们的问题 i>,不是你的。然后他们故意破坏 API,如果破坏了,那是他们自己的错。
@amkleist:您必须从 object
继承 property
才能正常工作:class Foo(object):
。【参考方案2】:
“名称修饰”支持 Python 中的前导双下划线命名约定。如您所见,这是通过在名称中插入 current 类的名称来实现的。
这对您意味着__getVal
形式的名称只能从完全相同的类中访问。如果您有一个嵌套类,它将受到不同的名称修饰。因此:
class Outer:
def foo(self):
print(self.__bar)
class Inner:
def foo2(self):
print(self.__bar)
在两个嵌套类中,名称将分别更改为_Outer__bar
和_Inner__bar
。
这不是 Java 的私有概念。它是“词汇隐私”(类似于“词汇范围”;-)。
如果您希望 Inner
能够访问 Outer
值,则必须提供一个未损坏的 API。可能是一个下划线:_getVal
,或者可能是一个公共方法:getVal
。
【讨论】:
您仍然可以从课堂外访问该名称。只需手动应用相同的转换;例如Inner.getVal()
可以修复为使用 self.__outer._Outer__getVal()
。以上是关于Python:从内部类访问外部私有函数的主要内容,如果未能解决你的问题,请参考以下文章