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


657

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

这是怎么回事?!

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

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

我在那里所做的是创建一个具有公共方法和私有方法的类,并将其实例化。

接下来,我将其称为public方法。

>>> obj.myPublicMethod()
public method

接下来,我尝试调用其私有方法。

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

这里的一切看起来都很好。我们无法调用它。实际上,它是“私有”的。好吧,实际上不是。在对象上运行dir()揭示了python为所有“私有”方法神奇地创建的新的神奇方法。

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

此新方法的名称始终是下划线,其后是类名,然后是方法名。

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

封装这么多,是吗?

无论如何,我总是会听到Python不支持封装,那么为什么还要尝试呢?是什么赋予了?


18
如果您使用反射,则Java或C#也是如此(这就是您在此处所做的事情)。
2015年

4
它是为单元测试目的而构建的,因此您可以使用该“ hack”来从外部对类的私有方法进行单元测试。
waas1919 '16

16
测试私有方法不是反模式吗?私有方法将在某些公共方法中使用,以确保其他方法永远不会被使用。测试私有方法的正确方法(根据我到目前为止对ThoughtWorks的了解)是,仅针对所有情况编写针对公共方法的测试。如果这样行之有效,则根本不需要从外部测试私有方法。
Vishnu Narang

3
@VishnuNarang:是的,这就是经常传授的内容。但是,一如既往,几乎“宗教”的方法“ 永远做到这一点,永远不要那样做”是唯一“永不”的好方法。如果单元测试“仅”用于回归测试或测试公共API,则无需测试私有。但是,如果您进行单元测试驱动的开发,则有充分的理由在开发过程中测试privat方法(例如,当很难通过公共接口模拟某些异常/极端参数时)。某些语言/单元测试环境不允许您这样做,恕我直言,这不好。
Marco Freudenberger

5
@MarcoFreudenberger我明白你的意思了。我在单元测试驱动的开发方面确实有经验。通常,当难以模拟参数时,通常是通过更改和改进设计来解决。我还没有遇到过这样一个场景,在该场景中,设计是完美的,但是仍然很难避免进行私有测试来进行单元测试。我会注意这种情况。谢谢。如果您可以分享一个方案来帮助我理解,我将不胜感激。
Vishnu Narang

Answers:


592

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

例如:

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

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


12
docs.python.org/2/tutorial/classes.html。第9.6节:私有变量和类本地引用。
gjain

72
对于我们这些懒于滚动/搜索的人:第9.6节直接链接
cod3monk3y 2014年

3
您应该使用单个下划线指定该变量应被视为私有变量。同样,这不会阻止某人实际访问它。
igon 2014年

14
Guido回答了这个问题 -“使(几乎)所有可发现的东西的主要原因是调试:调试时,您经常需要突破抽象”-我将其添加为注释,因为为时已晚-答案太多。
彼得(Peter M.)-代表莫妮卡

1
如果您遵循“防止故意访问”的标准,那么大多数OOP语言都不支持真正的私有成员。例如,在C ++中,您可以原始访问内存,而在C#中,受信任的代码可以使用私有反射。
CodesInChaos

207

私有功能的例子

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

12
self = MyClass() self.private_function()。:D当然,它在类中不起作用,但是您只需要定义一个自定义函数即可: def foo(self): self.private_function()
Casey Kuball 2012年

161
万一不清楚,那就是:永远不要在真实代码中这样做;)
Sudo Bash

10
@ThorSummoner或者function_call.startswith('self.')
nyuszika7h 2014年

13
inspect.stack()[1][4][0].strip()<-那些1、4和0幻数是什么?
akhy

5
通过执行此操作可以很容易地克服self = MyClass(); self.private_function()它,并且在使用x = self.private_function()方法内部调用时失败。
威尔

171

当我第一次从Java到Python时,我讨厌这一点。吓死我了。

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

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

请记住,封装与“安全性”或使孩子远离草坪之间的关系不大。这只是使代码库更易于理解的另一种模式。


36
@CamJackson Javascript是您的示例吗?唯一被广泛使用的语言具有基于原型的继承性,并且该语言支持函数式编程?我认为JS比大多数其他语言更难学习,因为它与传统OOP相比需要采取一些正交的步骤。并不是说这会阻止白痴写JS,他们只是不知道;)
K.Steff

23
API实际上是一个很好的例子,说明了为什么封装很重要以及何时首选私有方法。在以后的任何新版本中,旨在私有的方法都可能消失,更改签名或所有更改行为中最糟糕的情况-所有这些都不会发出警告。您聪明,成年的团队成员会真的记得她从一年后开始更新时使用的私有方法吗?她还会在那儿工作吗?
einnocent 2014年

2
我不同意这种说法。在生产代码中,我极有可能永远不会使用具有导致我更改公共成员使其“起作用”的错误的API。API应该可以使用。如果没有,我将提交错误报告或自行创建相同的API。我不喜欢这种哲学,我也不喜欢Python,尽管它的语法使用以下语言编写较小的脚本很有趣……
Yngve Sneen Lindal 2014年

4
Java具有Method.setAccessible和Field.setAccessible。还吓人吗?
Tony

15
用Java和C ++强制执行并不是因为Java不信任用户,而Python却不信任用户。这是因为,如果编译器和/或vm知道此信息,则在处理其业务方式时可以做出各种假设,例如C ++可以通过使用常规的旧C调用而不是虚拟调用来跳过整个间接层。当您从事高性能或高精度工作时,问题很重要。Python本质上不能在不影响其动态性的情况下真正利用信息。两种语言都针对不同的事物,所以都不是“错误的”
Shayne

144

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

严格来说,私有方法可以在其类之外访问,只是不容易访问。Python中没有什么是真正私有的。在内部,私有方法和属性的名称会被随意修改和修改,以使它们的名称看起来不可访问。您可以通过名称_MP3FileInfo__parse访问MP3FileInfo类的__parse方法。承认这很有趣,然后保证永远不要在真实代码中做到这一点。出于某种原因,私有方法是私有的,但是与Python中的许多其他事物一样,私有方法最终是约定俗成的问题,而不是强制性的问题。


201
或如Guido van Rossum所说:“我们都是成年人。”

35
-1:这是错误的。双下划线永远都不会被用作私有。下面来自Alya的答案告诉您名称修饰语法的真正意图。真正的约定是单个下划线。
nosklo

2
仅尝试一个下划线,您将看到得到的结果。@nosklo
Billal Begueradj

93

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

如果您确实必须拥有私有的内容,则可以在扩展中实现它(例如,在C for CPython中)。但是,在大多数情况下,您只是学习Python的做事方式。


所以我应该使用某种包装协议来访问受保护的变量吗?
直觉

3
没有“受保护”的变量比没有“私有”的变量多。如果要访问以下划线开头的属性,则可以执行此操作(但请注意,作者不建议这样做)。如果必须访问以双下划线开头的属性,则可以改名,但是几乎可以肯定您不希望这样做。
托尼·梅耶

33

并不是说您绝对不能绕开任何语言的成员私有性(C ++中的指针算术,.NET / Java中的反射)。

关键是,如果您尝试偶然调用私有方法,则会出错。但是,如果您想用脚射击自己,那就继续吧。

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


2
一点也不。我只是想指出,给开发人员一种简单而神奇的访问“私有”属性的方式是奇怪的。
willurd

2
是的,我只是想说明这一点。将其设为私有只是通过使编译器抱怨来表明“您不应该直接访问它”。但是,一个人真的想做到这一点。但是,是的,在Python中比在大多数其他语言中更容易。
Maximilian

7
在Java中,您实际上可以通过封装来保护内容,但是这要求您要聪明,并在SecurityManager中运行不受信任的代码,并且要非常小心。甚至Oracle有时也会出错。
锑2013年

12

class.__stuff命名约定可以让程序员知道他是不是要访问__stuff外部。改名这个名字使任何人都不太可能偶然地这样做。

没错,您仍然可以解决此问题,它甚至比其他语言还容易(顺便说一句,BTW也允许您这样做),但是如果Python程序员关心封装,那么他将不会这样做。


12

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

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

from bar import *

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


12

这只是这些语言设计选择之一。在某种程度上,它们是合理的。他们做到了,所以您需要走很长的路才能尝试调用该方法,如果真的很需要它,则必须有充分的理由!

我想到了调试挂钩和测试作为可能的应用程序,它们当然是负责任地使用的。


4

在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'

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

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

即使问题很旧,我也希望我的摘录对您有所帮助。


2

关于私有方法和属性的最重要的考虑是告诉开发人员不要在类之外调用它,这就是封装。人们可能会误解封装的安全性。当一个人故意使用您提到的那种语法时,您就不需要封装。

obj._MyClass__myPrivateMethod()

我已经从C#迁移了,起初对我来说也很奇怪,但是过了一会儿我才想到,只有Python代码设计人员对OOP的思考方式有所不同。


1

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.