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__getValInner 中的任何此类名称都会发生同样的情况,因此您的 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.attributeobj.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:从内部类访问外部私有函数的主要内容,如果未能解决你的问题,请参考以下文章

java 内部类

外部类与内部类的访问

内部类

JAVA 中外部类可以访问非静态内部类的私有属性???

内部类

面向对象---内部类