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()


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

>>> obj.myPublicMethod()
public method


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

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


封装这么多,是吗?

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

评论

如果您使用反射,则Java或C#同样适用(这就是您在此处所做的事情)。

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

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

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

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

#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节关于私有变量和类本地引用。

– gjain
13-10-17在0:41

对于那些懒得滚动/搜索的人:9.6节直接链接

– cod3monk3y
2014-02-20 4:22



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

– igon
2014年9月29日下午21:31

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

–Peter M.-代表莫妮卡(Monica)
16年2月1日在19:02

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

– CodesInChaos
16年7月13日在10:40

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


评论


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

–Casey Kuball
2012年5月8日在16:34



以防万一还不清楚:永远不要在真实代码中这样做;)

– Sudo Bash
13年7月12日在15:40

@ThorSummoner或者只是function_call.startswith('self。')。

–nyuszika7h
2014年8月27日在8:52

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

– akhy
16年6月23日在7:36

通过self = MyClass()可以很容易地克服这一点。 self.private_function(),当在方法内部使用x = self.private_function()调用时失败。

–将
18-11-22在2:58

#3 楼

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

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

我喜欢在一个平台上,人们互相信任,互相信任感觉他们不需要围绕自己的代码构建坚不可摧的墙。在高度封装的语言中,如果API有错误,并且您找出了问题所在,则可能仍无法解决它,因为所需的方法是私有的。在Python中,态度是:“确定”。如果您认为自己了解这种情况,也许您甚至已经读过它,那么我们所能说的就是“祝你好运!”。请记住,封装与“安全性”或保持孩子们离开草坪。这只是使代码库更易于理解的另一种模式。

评论


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

– K.Steff
2012年4月10日上午1:45

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

–einnocent
2014年1月10日,0:35

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

–Yngve Sneen Lindal
14年2月19日在11:45

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

–托尼
2014年4月24日在17:15

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

– Shayne
16/09/4在11:25



#4 楼

从http://www.faqs.org/docs/diveintopython/fileinfo_private.html


严格来说,私有方法可以在类之外访问,只是
不容易获得。
Python没有什么是真正的私有的。在内部,私有方法和
属性的名称会被即时修改和取消修饰
,以使它们似乎无法通过其给定名称进行访问。您
可以通过名称
永远不要在真实代码中做到这一点。
私有方法是出于
原因而私有的,但是与
Python中的许多其他事情一样,它们的私密性最终是惯例问题,而不是武力。


评论


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

–user3850
08年10月6日在11:20

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

– nosklo
09-10-15在14:02

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

– Billal Begueradj
17年2月7日在9:56

#5 楼

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

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

评论


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

–直觉
2010年5月10日18:56

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

–托尼·迈尔(Tony Meyer)
2010年5月13日上午8:41

#6 楼

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

要点是,如果您尝试偶然调用私有方法。但是,如果您想朝自己的脚开枪,那就继续吧。

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

评论


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

– Willurd
08年9月16日在9:11

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

– Maximilian
08/09/16在9:26

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

–锑
13年4月24日在1:16

#7 楼

class.__stuff的命名约定使程序员知道他无意从外部访问__stuff。 mangling这个名字使任何人都不太可能会意外地做到这一点。

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

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


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


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


即使问题很旧,我希望我的代码段也能有所帮助。

#11 楼

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

obj._MyClass__myPrivateMethod()


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

#12 楼

重要更新(Python 3.4):
任何形式为__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
 


#13 楼


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


据我所知,它们不能是私有的。

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