为什么Python的"私有"方法实际上不是私有的?

wil*_*urd 632 python encapsulation information-hiding python-2.7

Python使我们能够通过在名称前加上双下划线来在类中创建"私有"方法和变量,如下所示:__myPrivateMethod().那么,如何解释这一点呢

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
>>> obj.myPublicMethod()
public method
>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
>>> obj._MyClass__myPrivateMethod()
this is private!!
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?!

我会对那些没有那么做的人解释一下.

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
Run Code Online (Sandbox Code Playgroud)

我在那里做的是使用公共方法和私有方法创建一个类并实例化它.

接下来,我称之为公共方法.

>>> obj.myPublicMethod()
public method
Run Code Online (Sandbox Code Playgroud)

接下来,我尝试调用它的私有方法.

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
Run Code Online (Sandbox Code Playgroud)

这里的一切都很好看; 我们无法称呼它.事实上,它是"私人的".嗯,实际上并非如此.在对象上运行dir()会显示一个新的神奇方法,即python为所有"私有"方法创建神奇的方法.

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
Run Code Online (Sandbox Code Playgroud)

这个新方法的名称始终是下划线,后跟类名,后跟方法名.

>>> obj._MyClass__myPrivateMethod()
this is private!!
Run Code Online (Sandbox Code Playgroud)

封装如此之多,嗯?

无论如何,我总是听说Python不支持封装,所以为什么要试试呢?是什么赋予了?

小智 576

名称加扰用于确保子类不会意外地覆盖其超类的私有方法和属性.它的目的不是为了防止来自外部的故意访问.

例如:

>>> 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}
Run Code Online (Sandbox Code Playgroud)

当然,如果两个不同的类具有相同的名称,它就会崩溃.

  • 对于我们这些懒得滚动/搜索的人:[第9.6节直接链接](http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references) (70认同)
  • [Guido回答了这个问题](https://plus.google.com/115212051037621986145/posts/7wpbQTPRWft) - "使(几乎)所有可被发现的主要原因是调试:调试时经常需要突破抽象" - 我将其添加为评论因为为时已晚 - 答案太多了. (14认同)
  • http://docs.python.org/2/tutorial/classes.html.部分:9.6关于私有变量和类本地引用. (12认同)
  • 您应该放置一个下划线来指定该变量应被视为私有.同样,这并不妨碍某人实际访问它. (3认同)
  • 如果按照“防止故意访问”标准,大多数 OOP 语言不支持真正的私有成员。例如,在 C++ 中,您可以原始访问内存,而在 C# 中,受信任的代码可以使用私有反射。 (2认同)

小智 204

私有功能的例子

import re
import inspect

class MyClass :

    def __init__(self) :
        pass

    def private_function ( self ) :
        try :
            function_call = inspect.stack()[1][4][0].strip()

            # See if the function_call has "self." in the begining
            matched = re.match( '^self\.', function_call )
            if not matched :
                print 'This is Private Function, Go Away'
                return
        except :
            print 'This is Private Function, Go Away'
            return

        # This is the real Function, only accessible inside class #
        print 'Hey, Welcome in to function'

    def public_function ( self ) :
        # i can call private function from inside the class
        self.private_function()

### End ###
Run Code Online (Sandbox Code Playgroud)

  • 以防万一不清楚:*从不*在实际代码中执行此操作;) (157认同)
  • `inspect.stack()[1] [4] [0] .strip()`< - 那些1,4和0幻数是什么? (13认同)
  • `self = MyClass()``self.private_function()`.:D当然它在类中不起作用,但你只需要定义一个自定义函数:`def foo(self):self.private_function()` (12认同)
  • @ThorSummoner或只是`function_call.startswith('self.')`. (10认同)
  • 通过执行`self = MyClass();可以很容易地将其击败.self.private_function()`并且在方法中使用`x = self.private_function()`调用时失败. (5认同)
  • `self`只是一个惯例.可以为第一个参数使用不同的名称 (3认同)
  • 我不打扰加载正则表达式来测试一个`/^.../`表达式,节省一些时间并使用`0 == function_call.index('self.') (2认同)
  • @arun,为什么1 4 0? (2认同)
  • @Shuklaswag这使得您的代码更难以使用,因为它会阻碍任何人调试它.试图用不必要的聪明来绕过语言规则很少会很好地结束.(它也没有真正阻止对该函数的公共调用). (2认同)
  • 不确定我是否理解这个答案。它演示了如何拥有真正私有的方法,但没有回答“为什么 pyhthon 没有真正私有方法?”的问题,对吗? (2认同)

Tho*_*hle 160

当我第一次从Java转向Python时,我讨厌这个.它让我害怕死亡.

今天它可能只是我最喜欢Python 的一件事.

我喜欢在一个平台上,在这个平台上,人们相互信任,并且不觉得他们需要在代码周围建立难以穿透的墙.在强封装的语言中,如果API有错误,并且您已经找出问题所在,那么您可能仍然无法解决它,因为所需的方法是私有的.在Python中,态度是:"确定".如果你认为你了解情况,也许你甚至已经阅读过,那么我们所能说的就是"祝你好运!".

请记住,封装甚至与"安全"无关,或者让孩子远离草坪.它只是应该用于使代码库更容易理解的另一种模式.

  • @CamJackson Javascript就是你的例子?唯一广泛使用的语言,它具有基于prototybe的继承和支持函数式编程的语言?我认为JS比大多数其他语言更难学,因为它需要传统OOP的几个正交步骤.这不是为了防止白痴写JS,他们只是不知道它;) (35认同)
  • API实际上是一个非常好的例子,说明为什么封装很重要以及何时首选私有方法.一个旨在私有的方法可能会在任何后续新版本中消失,更改签名或最糟糕的所有更改行为 - 所有这些都不会发出警告.您的智能成人团队成员是否真的记得她在您更新后的一年内访问了一个有意成为私人的方法?她甚至会在那里工作吗? (21认同)
  • Java和C++中的实施并不是因为Java在Python的同时不信任用户.这是因为编译器和/或vm在处理它的业务如何知道这些信息时可以做出各种假设,例如C++可以通过使用常规的旧C调用而不是虚拟调用来跳过整个间接层,并且当你从事高性能或高精度的工作时很重要.Python本质上无法真正充分利用信息而不会影响其动态性.两种语言都针对不同的东西,所以两者都不是"错误的" (15认同)
  • Java有Method.setAccessible和Field.setAccessible.还吓人? (4认同)
  • “_我喜欢在一个平台上,人们互相信任,并且觉得他们不需要在代码周围建立坚不可摧的墙。_”完全是胡说八道。抛弃既定的设计原则并不会让你的同事爱上你,恰恰相反。从很多方面来说,Python 永远无法实现类型化 JVM 语言的结构和形式主义,或者 Haskell 的功能天赋。它对某些事情有好处,但没必要给猪涂口红。 (3认同)
  • 我不同意这种说法。在生产代码中,我极有可能永远不会使用具有导致我更改公共成员使其“起作用”的错误的API。API应该可以使用。如果没有,我将提交错误报告或自行创建相同的API。我不喜欢这种哲学,我也不是很喜欢Python,尽管它的语法使用以下语言编写较小的脚本变得很有趣。 (2认同)
  • 没有人强迫您使用未记录的 API,但如果您想要,谁会告诉您不可以呢?单元测试作为一个用例出现在我的脑海中。 (2认同)
  • 我和你的评论相反。我来自 python,并开始学习 Java 和 C++。私密并不是不信任你的队友。没有发生常见错误,即团队中的新开发人员访问业务逻辑范围之外的方法。应该是私有的并通过某种逻辑专门调用的东西现在是公共的并且可以从外部调用。更可怕的是,犯这个错误的开发人员是在公共端点上做的,打开一个应用程序。漏洞。我喜欢python,但我讨厌它的这一方面。 (2认同)
  • 它最明确。可以是一秒钟。缺陷。开发人员在团队中来来去去。开发人员可以负责添加一个公共端点,允许附属机构传递新的注册人以创建用户。然而,说开发人员,重新利用其他开发人员完成的帐户创建。帐户创建类调用了几种验证域是否存在欺诈的方法。但是这个新开发者直接访问了最终的方法,传入了一个用于域验证的值。现在公共端点绕过欺诈检查。如果最后一个方法是私有的,那么这些都不会发生。它会迫使它流过总线。逻辑。 (2认同)
  • 为了安全起见,我们不会将方法设为私有和公开。我们这样做是为了防止用户使用 volatile 方法致命伤害状态和编写程序.. 公共方法代表一个契约,如果我给你 A 你将输出 B。私有方法不代表一个契约。我可以根据需要更改将 A 转换为 B 的逻辑,只要交互的接口仍然满足约定。如果我允许您访问私有方法,您将使用它们。然后当我改变实现时,你的程序就会中断。不会发生在形成契约的公共方法上。 (2认同)

xsl*_*xsl 141

来自http://www.faqs.org/docs/diveintopython/fileinfo_private.html

严格来说,私人方法可以在课堂外访问,但不容易访问.Python中没有任何东西是真正私有的; 在内部,私有方法和属性的名称在运行中被破坏和解开,使它们看起来无法通过给定的名称访问.您可以通过名称_MP3FileInfo__parse访问MP3FileInfo类的__parse方法.承认这很有趣,然后承诺永远不会在真正的代码中做到这一点.私有方法是私有的,但与Python中的许多其他东西一样,它们的私有性最终是一个惯例,而不是力.

  • 或者像Guido van Rossum所说:"我们都是成年人." (196认同)
  • -1:这是错的.双重下划线绝不是首先用作私人的.以下Alya的答案告诉了名称修改语法的真实意图.真正的约定是单个下划线. (35认同)
  • 只尝试一个下划线,你会看到你得到的结果.@nosklo (2认同)

Ton*_*yer 89

常用的短语是"我们都是在这里同意成年人".通过预先添加单个下划线(不暴露)或双下划线(隐藏),您告诉您的类的用户您希望该成员以某种方式"私有".但是,除非他们有令人信服的理由(例如调试器,代码完成),否则你相信其他所有人都要负责任并尊重这一点.

如果你真的必须拥有私有的东西,那么你可以在扩展中实现它(例如在CP中用于CPython).但是,在大多数情况下,您只需学习Pythonic的做事方式.

  • 没有"受保护"的变量比"私人"更多.如果要访问以下划线开头的属性,您可以这样做(但请注意,作者不鼓励这样做).如果你必须访问以双下划线开头的属性,你可以自己修改名称,但你几乎肯定不想这样做. (3认同)

Max*_*ian 32

这并不像你绝对不能用任何语言来解决成员的私密性(C++中的指针算术,.NET/Java中的思考).

关键是如果您尝试偶然调用私有方法,则会出现错误.但如果你想用脚射击自己,那就去做吧.

编辑:你没有尝试通过OO封装来保护你的东西,对吗?

  • 在Java中,您实际上可以通过封装来保护内容,但这需要您智能并在SecurityManager中运行不受信任的代码,并且要非常小心.甚至甲骨文有时也会弄错. (6认同)
  • 一点也不。我只是想指出,给开发人员一种简单而神奇的访问“私有”属性的方式是奇怪的。 (2认同)
  • 是的,我只是想说明这一点。将其设为私有只是通过使编译器抱怨来表明“您不应该直接访问它”。但是,一个人真的想做到这一点。但是,是的,在Python中比在大多数其他语言中更容易。 (2认同)

Mor*_*jad 13

重要的提示:

任何形式的标识符__name(至少两个前导下划线,最多一个尾随下划线)都被公开替换为_classname__name,其中classname是去除前导下划线的当前类名。

因此,__name是私有的,_classname__name而是公共的。

这并不意味着您可以保护您的私人数据,因为它可以通过更改变量名称轻松访问。

https://docs.python.org/3/tutorial/classes.html#tut-private

例子

class Cat:
    def __init__(self, name='unnamed'):
        self.name = name
    def __print_my_name(self):
        print(self.name)
        
        
tom = Cat()
tom.__print_my_name() #Error
tom._Cat__print_my_name() #Prints name
Run Code Online (Sandbox Code Playgroud)

  • 这不是一个重要的更新,它一直都是如此。将链接中的 3 替换为 2,https://docs.python.org/2/tutorial/classes.html#tut-private,显示的文本几乎与您的答案完全相同。不是更新。 (3认同)

Nic*_*lay 12

class.__stuff命名约定可以让程序员知道他是不是要访问__stuff外部.名称错误使得任何人都不可能偶然做到这一点.

没错,你仍然可以解决这个问题,它比其他语言更容易(BTW也允许你这样做),但如果他关心封装,没有Python程序员会这样做.


ctc*_*rry 12

这只是其中一种语言设计选择.在某种程度上,他们是合理的.他们制作它所以你需要走得很远,试着调用这个方法,如果你真的非常需要它,你必须有一个很好的理由!

调试钩子和测试作为可能的应用程序浮现在脑海中,当然负责任地使用.


Ros*_*oss 12

当模块属性名称以单个下划线(例如_foo)开头时,存在类似的行为.

使用该from*方法时,不会将这样命名的模块属性复制到导入模块中,例如:

from bar import *
Run Code Online (Sandbox Code Playgroud)

但是,这是一种约定而不是语言约束.这些不是私人属性; 它们可以被任何进口商引用和操纵.有人认为,由于这个原因,Python无法实现真正​​的封装.


Afs*_*iri 5

关于私有方法和属性最重要的关注点是告诉开发人员不要在类之外调用它,这就是封装。人们可能会误解封装的安全性。当一个人故意使用像你提到的(下面)这样的语法时,你就不需要封装。

obj._MyClass__myPrivateMethod()
Run Code Online (Sandbox Code Playgroud)

我已经从 C# 迁移过来,一开始这对我来说也很奇怪,但过了一段时间我就意识到,只有 Python 代码设计者看待 OOP 的方式有所不同。