【问题标题】:Why are Python's 'private' methods not actually private?为什么 Python 的“私有”方法实际上不是私有的?
【发布时间】:2010-09-09 09:21:41
【问题描述】:

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 "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

>>> obj._MyClass__myPrivateMethod()
this is private!!

怎么了?!

我会为那些不太明白的人解释一下。

>>> 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 "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

这里的一切看起来都不错;我们无法调用它。事实上,它是“私人的”。嗯,实际上并非如此。在对象上运行 dir() 揭示了 Python 为所有“私有”方法神奇地创建的新神奇方法。

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

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

>>> obj._MyClass__myPrivateMethod()
this is private!!

封装这么多,嗯?

无论如何,我一直听说 Python 不支持封装,那么为什么还要尝试呢?什么给了?

【问题讨论】:

  • 如果您使用反射,Java 或 C# 也是如此(这就是您在那里所做的)。
  • 它是为单元测试目的而构建的,因此您可以使用该“hack”从外部对您的类的私有方法进行单元测试。
  • 测试私有方法不是反模式吗?私有方法将在某些公共方法中使用,以确保它永远不会被使用。测试私有方法的正确方法(基于我迄今为止从 ThoughtWorks 学到的知识)是只为覆盖所有情况的公共方法编写测试。如果一切正常,您根本不需要从外部测试私有方法。
  • @VishnuNarang:是的,这是经常被教导的。但与往常一样,“总是这样做,从不那样做”几乎是“宗教”的方法是唯一“从不”好的事情。如果单元测试“仅”用于回归测试或测试公共 API,则不需要测试私有。但是,如果您进行单元测试驱动开发,那么在开发过程中测试私有方法是有充分理由的(例如,当很难通过公共接口模拟某些异常/极端参数时)。一些语言/单元测试环境不允许你这样做,恕我直言不好。
  • @MarcoFreudenberger 我明白你的意思。我确实有单元测试驱动开发的经验。通常,当模拟参数变得困难时,通常可以通过更改和改进设计来解决。我还没有遇到过设计完美但单元测试仍然很难避免测试私有方法的场景。我会留意这种情况。谢谢。如果您能分享一个场景以帮助我理解,我将不胜感激。

标签: python python-2.7 encapsulation information-hiding


【解决方案1】:

名称加扰用于确保子类不会意外覆盖其超类的私有方法和属性。它并非旨在防止从外部故意访问。

例如:

>>> 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}

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

【讨论】:

  • docs.python.org/2/tutorial/classes.html。第 9.6 节关于私有变量和类本地引用。
  • 对于我们这些懒得滚动/搜索的人:Section 9.6 direct link
  • 您应该使用一个下划线来指定该变量应被视为私有变量。同样,这并不能阻止某人实际访问它。
  • Guido answered this question - “使(几乎)所有东西都可发现的主要原因是调试:在调试时,您经常需要突破抽象” - 我将其添加为注释,因为为时已晚 - 太多了答案。
  • 如果按照“防止故意访问”标准,大多数 OOP 语言不支持真正的私有成员。例如,在 C++ 中,您可以对内存进行原始访问,而在 C# 中,受信任的代码可以使用私有反射。
【解决方案2】:

私有函数示例

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 beginning
            matched = re.match( '^self\.', function_call)
            if not matched:
                print 'This is a private function. Go away.'
                return
        except:
            print 'This is a private function. Go away.'
            return

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

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

### End ###

【讨论】:

  • self = MyClass()self.private_function()。 :D 当然它在类中不起作用,但你只需要定义一个自定义函数:def foo(self): self.private_function()
  • 以防万一不清楚:从不在实际代码中这样做;)
  • @ThorSummoner 或者只是function_call.startswith('self.')
  • inspect.stack()[1][4][0].strip()
  • 这可以通过 self = MyClass(); self.private_function() 轻松解决,而在方法中使用 x = self.private_function() 调用时会失败。
【解决方案3】:

当我第一次从 Java 转向 Python 时,我讨厌这个。吓死我了。

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

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

请记住,封装与“安全”或让孩子远离草坪甚至都没有微弱的关系。它只是另一种模式,应该用于使代码库更易于理解。

【讨论】:

  • @CamJackson Javascript 是你的例子??唯一广泛使用的具有基于原型的继承和支持函数式编程的语言?我认为 JS 比大多数其他语言更难学习,因为它与传统的 OOP 相比需要几个正交步骤。并不是说这会阻止白痴编写 JS,他们只是不知道;)
  • API 实际上是一个很好的例子,说明了为什么封装很重要以及何时首选私有方法。在任何后续新版本中,旨在私有的方法可能会消失、更改签名或最糟糕的更改行为——所有这些都没有警告。一年后,当你更新时,你聪明的成年团队成员是否真的记得她访问了一个打算保密的方法?她还会再在那里工作吗?
  • 我不同意这个论点。在生产代码中,我很可能永远不会使用有错误的 API,该错误使我更改公共成员以使其“工作”。 API 应该可以工作。如果没有,我会提交错误报告或自己制作相同的 API。我不喜欢这种哲学,我也不是很喜欢 Python,尽管它的语法使得在...中编写较小的脚本很有趣。
  • Java 有 Method.setAccessible 和 Field.setAccessible。还吓人?
  • Java 和 C++ 中的强制执行并不是因为 Java 不信任用户而 Python 不信任用户。这是因为编译器和/或虚拟机在处理它如何处理其业务时可以做出各种假设,如果它知道这些信息,例如 C++ 可以通过使用常规的旧 C 调用而不是虚拟调用来跳过整个间接层,并且当您从事高性能或高精度工作时,这很重要。 Python 就其本质而言,无法在不损害其活力的情况下真正充分利用信息。两种语言都针对不同的事物,所以都没有“错误”
【解决方案4】:

来自Dive Into Python, 3.9. Private functions

严格来说,私有方法是 在他们的课外访问,只是 不容易接近。什么都没有 Python 是真正私有的;在内部, 私有方法的名称和 属性被损坏和未损坏 在飞行中让它们看起来 无法通过他们的名字访问。你 可以访问 __parse 方法 MP3FileInfo 类的名称 _MP3FileInfo__解析。承认这很有趣,然后承诺 永远,永远在真正的代码中做到这一点。 私有方法是私有的 原因,但就像其他许多事情一样 Python,他们的隐私是 最终是约定俗成的问题,而不是 力。

【讨论】:

  • 或者正如 Guido van Rossum 所说:“我们都是成年人。”
  • -1:这是错误的。双下划线从不打算首先用作私有。下面 Alya 的回答说明了名称修饰语法的真正意图。真正的约定是一个下划线。
  • 只用一个下划线试试,你会看到你得到的结果。 @nosklo
【解决方案5】:

常用的短语是“我们在这里都是同意的成年人”。通过在前面添加一个下划线(不要暴露)或双下划线(隐藏),您是在告诉您班级的用户您希望该成员以某种方式成为“私人”。但是,您相信其他所有人都会负责任地行事并尊重这一点,除非他们有令人信服的理由不这样做(例如,调试器和代码完成)。

如果您确实必须拥有私有的东西,那么您可以在扩展中实现它(例如,在 C 中表示 CPython)。然而,在大多数情况下,您只需学习Pythonic 的做事方式。

【讨论】:

  • 那么我应该使用某种包装协议来访问受保护的变量吗?
  • 没有“受保护”变量,也没有“私有”变量。如果您想访问以下划线开头的属性,您可以这样做(但请注意,作者不鼓励这样做)。如果您必须访问以双下划线开头的属性,您可以自己修改名称,但您几乎肯定不想这样做。
【解决方案6】:

这并不是说您绝对无法解决任何语言中成员的隐私问题(C++ 中的指针算法和 .NET/Java 中的反射)。

关键是如果你不小心调用了私有方法,你会得到一个错误。但是,如果您想在脚上开枪,那就去做吧。

您不会尝试通过 OO 封装来保护您的东西,是吗?

【讨论】:

  • 一点也不。我只是想说,给开发人员提供一种简单且在我看来很神奇的方式来访问“私有”属性是很奇怪的。
  • 是的,我只是试图说明这一点。通过让编译器抱怨,将其设为私有只是说“你不应该直接访问它”。但是一个人想要真正做到他能做到。但是,是的,Python 比大多数其他语言更容易。
  • 在 Java 中,您实际上可以通过封装来保护东西,但这需要您聪明并在 SecurityManager 中运行不受信任的代码,并且要非常小心。甚至 Oracle 有时也会出错。
【解决方案7】:

重要提示:

__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

【讨论】:

  • 这不是一个重要的更新,它一直都是如此。将链接中的 3 替换为 2,docs.python.org/2/tutorial/classes.html#tut-private,显示的文本几乎与您的答案完全相同。不是更新。
  • 这也是不正确的:__name 不是“私有的”,因为它可以使用_classname__name 访问。可访问的东西不是“私人的”。在任何主要的计算机语言(AFAIK)中都没有“私有”属性这样的东西,就这么简单。您可以说方法中的局部变量是“私有的”,但前提是它们不能以任何方式从方法外部访问。
  • 答案中已经解释过了。然而,有了这个定义,并说“在任何主要的计算机语言中都没有私有属性这样的东西”,那么我们必须在编程语言中重新定义“私有”。但在那之前,我们可以使用我们拥有的并将其称为私有,因为官方文件称其为“私有”。
【解决方案8】:

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

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

from bar import *

但是,这是一种约定,而不是语言限制。这些不是私有属性;它们可以被任何进口商引用和操作。有人认为,正因如此,Python 无法实现真正​​的封装。

【讨论】:

    【解决方案9】:

    这只是其中一种语言设计选择。在某种程度上,他们是有道理的。他们做到了,所以你需要不遗余力地尝试调用该方法,如果你真的那么需要它,你一定有一个很好的理由!

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

    【讨论】:

      【解决方案10】:

      在 Python 3.4 中,行为如下:

      >>> class Foo:
              def __init__(self):
                      pass
              def __privateMethod(self):
                      return 3
              def invoke(self):
                      return self.__privateMethod()
      
      
      >>> help(Foo)
      Help on class Foo in module __main__:
      
      class Foo(builtins.object)
       |  Methods defined here:
       |
       |  __init__(self)
       |
       |  invoke(self)
       |
       |  ----------------------------------------------------------------------
       |  Data descriptors defined here:
       |
       |  __dict__
       |      dictionary for instance variables (if defined)
       |
       |  __weakref__
       |      list of weak references to the object (if defined)
      
       >>> f = Foo()
       >>> f.invoke()
       3
       >>> f.__privateMethod()
       Traceback (most recent call last):
         File "<pyshell#47>", line 1, in <module>
           f.__privateMethod()
       AttributeError: 'Foo' object has no attribute '__privateMethod'
      

      来自9.6. Private Variables

      请注意,修改规则主要是为了避免事故; 仍然可以访问或修改被认为是私有的变量。这甚至在特殊情况下很有用,例如在调试器中。

      【讨论】:

        【解决方案11】:

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

        obj._MyClass__myPrivateMethod()
        

        我是从 C# 迁移过来的,一开始我也觉得很奇怪,但过了一段时间,我意识到只有 Python 代码设计人员对 OOP 的看法不同。

        【讨论】:

          【解决方案12】:

          为什么 Python 的“私有”方法实际上并不是私有的?

          据我了解,它们不能是私有的。如何保护隐私?

          显而易见的答案是“只能通过self 访问私有成员”,但这不起作用——self 在 Python 中并不特殊。它只不过是函数第一个参数的常用名称。

          【讨论】:

            猜你喜欢
            • 2011-10-22
            • 2011-03-07
            • 2012-02-28
            • 1970-01-01
            • 2011-10-23
            • 1970-01-01
            • 2011-12-12
            • 2014-06-03
            • 1970-01-01
            相关资源
            最近更新 更多