为什么Python的“私有”方法实际上并不是私有的?
为什么Python的“私有”方法实际上并不是私有的?
Python允许我们在类中使用双下划线作为前缀,创建“私有”的方法和变量,例如:__myPrivateMethod()。那么,怎么解释下面的情况呢?\nclass MyClass:\n def myPublicMethod(self):\n print \'public method\'\n \n def __myPrivateMethod(self):\n print \'this is private!!\'\n \nobj = MyClass()\nobj.myPublicMethod()\npublic method\nobj.__myPrivateMethod()\nTraceback (most recent call last):\n File \"
Python的'private'方法实际上不是私有的,这是因为Python并没有提供真正的私有方法。在Python中,对于类中以双下划线开头的方法,默认被认为是私有方法,但实际上仍然可以通过一些方法来访问这些方法。原因是Python并没有严格限制对私有方法的访问,这是出于Python的设计哲学之一:鼓励开放和透明。Python的设计者认为开发者应该信任使用类中的方法,而不是试图绕过访问限制。
虽然Python没有提供真正的私有方法,但是可以使用一些技巧来模拟私有方法。上面的代码示例就是一个例子,通过使用inspect模块和正则表达式来判断是否是私有方法,并在访问私有方法时进行限制。但是这种方法并不是一种推荐的实践,因为它会增加代码的复杂性,并且可以通过一些技巧来绕过访问限制。
要解决这个问题,可以使用命名约定来表示方法的访问级别。在Python中,以单下划线开头的方法被认为是受保护的方法,表示这些方法不应该被外部直接访问。以双下划线开头的方法被认为是私有的方法,表示这些方法不应该被外部直接访问,并且还有一些名称修饰的规则来防止在子类中被重写。
总之,Python的'private'方法实际上不是真正的私有方法,这是出于Python的设计哲学之一,即鼓励开放和透明。虽然可以使用一些技巧来模拟私有方法,但这并不是一种推荐的实践。在实际编码中,应该遵循命名约定来表示方法的访问级别,并信任使用类中的方法,而不是试图绕过访问限制。
为什么Python的“私有”方法实际上并不是私有的?
当我从Java转到Python时,我讨厌这一点。它吓死我了。
但现在它可能是我最喜欢Python的一件事。
我喜欢在一个人们互相信任、不觉得自己需要在代码周围建立不可逾越的障碍的平台上。在强封装的语言中,如果一个API有一个bug,并且你已经找出了问题出在哪里,你可能仍然无法解决它,因为需要的方法是私有的。在Python中,态度是:“当然”。如果你认为你理解了情况,甚至可能已经阅读过它,那么我们只能说:“祝你好运!”。
要记住,封装与“安全性”或者防止别人侵入的意义并没有丝毫关系。它只是应该用来使代码更易于理解的另一种模式。
API实际上是封装为什么很重要以及何时优先考虑私有方法的一个很好的例子。一个意图为私有的方法可能会在任何后续的新版本中消失、改变签名,或者最糟糕的是改变行为,而这些都是没有警告的。当你更新时,你聪明的成年团队成员真的会记得她一年前访问过一个意图为私有的方法吗?她还会在那里工作吗?
我不同意这个观点。在生产代码中,我可能永远不会使用一个有bug的API来改变公共成员以使其“工作”。一个API应该是可用的。如果它不可用,我会提交一个bug报告或者自己做一个相同的API。我不喜欢这个哲学,也不是非常喜欢Python,尽管它的语法使得编写小脚本很有趣...
Java有Method.setAccessible和Field.setAccessible。也令人担心吗?
Java和C++中的实施并不是因为Java不信任用户,而是因为编译器和/或虚拟机在处理业务时可以做出各种假设,如果它了解这些信息的话,例如C++可以通过使用普通的C调用而不是虚拟调用来跳过整个层次的间接调用,而在处理高性能或高精度的东西时,这是很重要的。Python本质上不能很好地利用这些信息而不损害其动态性。这两种语言的目标不同,所以都不是“错误的”。
我们不是为了安全性而使方法私有和公共。我们这样做是为了防止用户对状态造成致命伤害并编写使用易变方法的程序。公共方法代表一个契约,如果我给你A,你将输出B。私有方法不代表一个契约。只要交互的接口仍然满足契约,我可以根据需要更改将A转换为B的逻辑。如果我给你访问私有方法的权限,你将使用它们。然后当我改变实现时,你的程序就会出错。这种情况不会发生在形成契约的公共方法中。
Python的“私有”方法实际上并不是真正的私有,这是因为Python使用了名称混淆(name scrambling)来确保子类不会意外地重写其父类的私有方法和属性。它的设计并不是为了阻止外部故意访问。
例如:
class Foo(object): def __init__(self): self.__baz = 42 def foo(self): print self.__baz class Bar(Foo): def __init__(self): super(Bar, self).__init__() self.__baz = 21 def bar(self): print self.__baz x = Bar() x.foo() # 输出 42 x.bar() # 输出 21 print x.__dict__ # 输出 {'_Bar__baz': 21, '_Foo__baz': 42}
当然,如果两个不同的类具有相同的名称,这种方法就会失效。
这是Python官方文档中的一个链接:docs.python.org/2/tutorial/classes.html,其中第9.6节讲解了私有变量和类本地引用。
对于我们这些懒得滚动/搜索的人来说:第9.6节的直接链接
很好——一个积极的原因!我之前读到/听到的是这样的,这只是为了阻止外部故意访问(而不是真正阻止)。很高兴看到还有其他的好处。
你应该在变量前加一个下划线来指定它是私有的。但是,这并不能阻止某人实际访问它。
哦,是的,是这样的,他在文档中找到的这个答案。
Guido回答了这个问题——“使(几乎)所有东西都可以被发现的主要原因是调试:在调试时,你经常需要打破抽象”。我将其作为评论添加,因为太晚了——回答太多了。
如果按照“防止故意访问”的标准,大多数面向对象的编程语言都不支持真正的私有成员。例如,在C++中,你可以对内存进行原始访问,在C#中,受信任的代码可以使用私有反射。
这就是为什么我从不在由我设计的接口中使用双下划线的原因。每个可能会对其进行子类化的人都知道他们正在处理什么,并且很可能有理由重载某些东西,所以一个下划线足以使方法和属性私有化。然而,当我实现由其他人设计的接口时,我会使用双下划线,以避免混乱API并防止基于对其ABS的了解而进行的子类化时出现错误。使用双下划线的一个很好的理由是,例如,实现io.BufferedIOBase
,在其中不应该有任何干扰缓冲区的内容。
为什么使用双下划线进行名称混淆,而不使用单下划线?
是的,这就是关键。如果你来自Java等语言,你可以将双下划线大致映射为“私有”,将单下划线映射为“受保护”。