在Python中,对象名称前的单前导下划线和双前导下划线代表什么?


单个下划线

在类中,带前导下划线的名称向其他程序员表示该属性或方法将在该类中使用。然而,隐私权并未以任何方式强制执行。对模块中的函数使用前导下划线表示不应从其他地方导入。

从PEP-8风格指南:

_single_leading_underscore:“内部使用”指标较弱。例如,from M import*不会导入名称以下划线开头的对象。

双下划线(名称Mangling)

从Python文档中:

__spam形式的任何标识符(至少两个前导下划线,最多一个尾随下划线)在文本上替换为_classname__spam,其中classname是当前类名,前导下划线被去掉。这种修改是在不考虑标识符的语法位置的情况下完成的,因此它可以用于定义类私有实例和类变量、方法、存储在全局变量中的变量,甚至是存储在实例中的变量。对于其他类的实例,该类是私有的。

以及来自同一页的警告:

名称修改旨在为类提供一种简单的方法来定义“私有”实例变量和方法,而不必担心派生类定义的实例变量,也不必担心类外的代码干扰实例变量。注意,损坏规则的设计主要是为了避免事故;一个坚定的灵魂仍然有可能访问或修改一个被认为是私有的变量。

实例

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

单一前导下划线是一种惯例。如果名称是否以单个下划线开头,则从解释器的角度来看没有区别。

双前导和尾随下划线用于内置方法,如__init__、__bool__等。

双前导下划线不带尾随对应符也是一种惯例,但是,类方法将被解释器破坏。对于变量或基本函数名不存在差异。


_foo:只是惯例。程序员指示变量是私有的(无论在Python中意味着什么)。__foo:这有真正的意义。解释器将此名称替换为_classname__foo,以确保该名称不会与其他类中的类似名称重叠。__foo__:只是惯例。Python系统使用不会与用户名冲突的名称的方法。

在Python世界中,没有其他形式的下划线有意义。此外,在这些约定中,类、变量、全局等之间没有区别。


你的问题很好,不仅仅是关于方法。模块中的函数和对象通常也以一个下划线作为前缀,也可以以两个下划线作为后缀。

例如,__double_underscore名称在模块中不会被篡改。如果从一个模块(从模块导入*)导入所有名称,则不会导入以一个(或多个)下划线开头的名称,也不会导入帮助(模块)中显示的名称。


到目前为止,答案很好,但缺少一些花絮。一个前导下划线不仅仅是一个约定:如果使用from foobar import*,并且foobar模块没有定义__all__列表,那么从模块导入的名称不包括前导下划线的名称。假设这主要是一个惯例,因为这个案例是一个相当模糊的角落;-)。

前导下划线约定不仅广泛用于私有名称,也广泛用于C++所称的受保护的名称,例如,完全打算由子类重写的方法的名称(即使是必须重写的方法,因为在基类中它们引发NotImplementedError!-)通常是单前导下划线名称,以向使用该类(或子类)实例的代码指示不打算直接调用所述方法。

例如,要使线程安全队列具有与FIFO不同的排队规则,可以导入queue,将queue.queue子类化,并重写_get和_put等方法;“客户端代码”从不调用这些(“钩子”)方法,而是调用(“组织”)公共方法,如put和get(这被称为模板方法设计模式——例如,请参见此处,以获取基于我关于该主题的演讲视频的有趣演示,并添加了摘要)。

编辑:会谈描述中的视频链接现已断开。你可以在这里和这里找到前两个视频。


有时,您会看到一个带有前导下划线的元组,如

def foo(bar):
    return _('my_' + bar)

在本例中,发生的情况是,_()是本地化函数的别名,该函数根据语言环境对文本进行操作以将其转换为适当的语言等。例如,Sphinx这样做,您可以在导入中找到

from sphinx.locale import l_, _

在sphinx.locale中,_()被指定为某个本地化函数的别名。


.variable是半私有的,仅用于约定

.__variable通常被错误地认为是超级私有的,而它的实际含义只是为了命名mangle以防止意外访问[1]

.__variable__通常为内置方法或变量保留

您仍然可以访问__如果您非常想的话,可以对变量进行损坏。双下划线只是将变量命名为mangles或重命名为instance_类名__已损坏

例子:

class Test(object):
    def __init__(self):
        self.__a = 'a'
        self._b = 'b'

>>> t = Test()
>>> t._b
'b'

t.b是可访问的,因为它仅按惯例隐藏

>>> t.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'

找不到t.__a,因为它由于名称损坏而不再存在

>>> t._Test__a
'a'

通过访问实例_className__variable而不仅仅是双下划线名称,您可以访问隐藏值


如果真的想让变量成为只读的,IMHO最好的方法就是使用property(),只传递getter。有了property()我们可以完全控制数据。

class PrivateVarC(object):

    def get_x(self):
        pass

    def set_x(self, val):
        pass

    rwvar = property(get_p, set_p)  

    ronly = property(get_p) 

我知道OP问了一个稍有不同的问题,但由于我发现另一个问题是“如何设置私有变量”,标记为与此重复,所以我想在这里添加此附加信息。


下面是一个简单的示例,说明双下划线财产如何影响继承的类。因此,使用以下设置:

class parent(object):
    __default = "parent"
    def __init__(self, name=None):
        self.default = name or self.__default

    @property
    def default(self):
        return self.__default

    @default.setter
    def default(self, value):
        self.__default = value


class child(parent):
    __default = "child"

如果然后在python REPL中创建子实例,您将看到以下内容

child_a = child()
child_a.default            # 'parent'
child_a._child__default    # 'child'
child_a._parent__default   # 'parent'

child_b = child("orphan")
## this will show 
child_b.default            # 'orphan'
child_a._child__default    # 'child'
child_a._parent__default   # 'orphan'

这对一些人来说可能很明显,但在一个复杂得多的环境中,这让我措手不及


开头有一个下划线:

Python没有真正的私有方法。相反,方法或属性名称开头的一个下划线表示您不应该访问此方法,因为它不是API的一部分。

class BaseForm(StrAndUnicode):
    
    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

    errors = property(_get_errors)

(此代码片段取自django源代码:django/forms/forms.py)。在这段代码中,error是一个公共属性,但此属性调用的方法_get_errors是“私有”的,因此您不应该访问它。

开头有两个下划线:

这会引起很多混乱。它不应用于创建私有方法。应该使用它来避免方法被子类重写或意外访问。让我们看一个例子:

class A(object):
    def __test(self):
        print "I'm a test method in class A"

    def test(self):
        self.__test()
 
a = A()
a.test()
# a.__test() # This fails with an AttributeError
a._A__test() # Works! We can access the mangled name directly!

输出:

$ python test.py
I'm test method in class A
I'm test method in class A

现在创建一个子类B并为__test方法进行定制

class B(A):
    def __test(self):
        print "I'm test method in class B"

b = B()
b.test()

输出将是。。。。

$ python test.py
I'm test method in class A

正如我们所看到的,B.test()并没有像我们预期的那样调用B.__test()方法。但事实上,这是__的正确行为。名为__test()的两个方法会自动重命名(损坏)为_A__test()和_B__test(。当你创建一个以__开头的方法时,这意味着你不希望任何人能够覆盖它,你只想从它自己的类内部访问它。

开头和结尾有两个下划线:

当我们看到像__this___这样的方法时,不要调用它。这是python要调用的方法,而不是您。让我们来看看:

>>> name = "test string"
>>> name.__len__()
11
>>> len(name)
11

>>> number = 10
>>> number.__add__(40)
50
>>> number + 50
60

总是有一个运算符或本机函数调用这些神奇的方法。有时它只是在特定情况下python调用的钩子。例如,在调用__new__()构建实例后创建对象时调用__init__()。。。

让我们举个例子。。。

class FalseCalculator(object):

    def __init__(self, number):
        self.number = number

    def __add__(self, number):
        return self.number - number

    def __sub__(self, number):
        return self.number + number

number = FalseCalculator(20)
print number + 10      # 10
print number - 20      # 40

有关详细信息,请参阅[PEP-8指南][1]。有关更多神奇的方法,请参阅[本PDF][2]。[1]: https://www.python.org/dev/peps/pep-0008/#method-名称和实例变量[2] http://github.com/RafeKettler/magicmethods/blob/master/magicmethods.pdf


Python中不存在只能从对象内部访问的“私有”实例变量。然而,大多数Python代码都遵循一个惯例:前缀为下划线的名称(例如_spam)应被视为API的非公共部分(无论是函数、方法还是数据成员)。应将其视为实施细节,如有更改,恕不另行通知。

参考https://docs.python.org/2/tutorial/classes.html#private-变量和类本地引用


了解_和_的事实非常容易;其他答案很好地表达了这一点。这种用法很难确定。

我是这样看的:

_

应用于指示函数不供公共使用,例如API。这和导入限制使其行为与c#中的内部行为非常相似。

__

应用于避免继承层次结构中的名称冲突,并避免后期绑定。很像c#中的private。

==>

如果你想表明某些东西不是供公众使用的,但它应该像受保护的用途一样。如果你想表明某些东西不是公共使用的,但它应该像私人使用一样。

这也是我非常喜欢的一句话:

问题是,一个类的作者可能会合理地认为“属性/方法名称应该是私有的,只能从内部访问这个类定义”,并使用__private约定,该类的用户可以创建合法需要的子类访问该名称。所以要么必须修改超类(这可能很困难或不可能),或者子类代码必须使用手动损坏的名称(这充其量是丑陋和脆弱的)。

但在我看来,如果没有IDE在重写方法时发出警告,那么如果意外地从基类重写了方法,查找错误可能需要一段时间。


既然这么多人都在提到雷蒙德的谈话,我就把他说的话写下来,让事情变得简单一点:

双下划线的用意不是关于隐私。我们的意图是这样使用它类圆(对象):def__init__(自身,半径):self.radius=半径定义区域(自身):p=自身__周长()r=p/math.pi/2.0返回math.pi*r**2.0定义周长(自身):return 2.0*math.pi*self.radius__周界=周界#本地参考等级轮胎(圆形):定义周长(自身):返回圆周长(自身)*1.25这实际上是隐私的反面,都是关于自由的。它使您的子类可以自由覆盖任何一个方法,而不破坏其他方法。

假设您没有在Circle中保留周界的本地参考。现在,派生类Tire覆盖了周长的实现,而不接触面积。当您调用Tire(5).area()时,理论上它应该仍然使用Circle.perimeter进行计算,但实际上它使用的是Tire.perimet,这不是预期的行为。这就是为什么我们需要Circle的本地参考。

但为什么用周长代替周长?因为_perimeter仍然给派生类重写的机会:

class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

    _perimeter = perimeter

双下划线具有名称损坏,因此父类中的本地引用在派生类中被重写的可能性很小。因此“使您的子类可以自由覆盖任何一个方法而不破坏其他方法”。

如果您的类不会被继承,或者方法重写不会破坏任何东西,那么您就不需要__double_leading_underscore。


很好的答案,而且都是正确的。我提供了简单的例子以及简单的定义/含义。

含义:

某些变量--► 这是公开的,任何人都可以看到。

_某些变量--► 这是公开的,任何人都可以看到,但这是一个惯例,表明私人。。。警告Python不执行强制。

__某些变量--► Python将变量名替换为_classname__some_varable(AKA name mangling),并降低/隐藏其可见性,更像是私有变量。

老实说,根据Python文档

无法访问的“Private”实例变量Python中不存在对象“

示例:

class A():
    here="abc"
    _here="_abc"
    __here="__abc"


aObject=A()
print(aObject.here) 
print(aObject._here)
# now if we try to print __here then it will fail because it's not public variable 
#print(aObject.__here)

根据Python中下划线的含义

单前导下划线(_var):表示名称仅供内部使用的命名约定。通常不由Python解释器强制执行(通配符导入除外),仅作为程序员的提示。单尾下划线(var_):按惯例使用,以避免与Python关键字的命名冲突。双前导下划线(__var):在类上下文中使用时触发名称更改。由Python解释器强制执行。双前导和尾随下划线(__var__):表示Python语言定义的特殊方法。避免为自己的属性使用此命名方案。单下划线(_):有时用作临时或不重要变量的名称(“不在乎”)。另外:Python REPL中最后一个表达式的结果。


对于方法,可以使用双下划线隐藏私有“方法”,模式如下:

# Private methods of MyClass
def _MyClass__do_something(obj:'MyClass'):
    print('_MyClass__do_something() called. type(obj) = {}'.format(type(obj)))

class MyClass():
    def __init__(self):
        __do_something(self)

mc = MyClass()

输出:

_MyClass__do_something() called. type(obj) = <class '__main__.MyClass'>

我今天在尝试对类方法使用双下划线时遇到了这个问题,并得到了NameError:name“_<class><method>”未定义错误。


_var:python中带前导单下划线的变量是经典变量,旨在通知使用代码的其他人该变量应保留供内部使用。它们与经典变量有一点不同:在对定义它们的对象/模块进行通配符导入时,不会导入它们(定义__all__变量时除外)。如:#foo.pyvar=“var”_var=“_var”#巴.py从foo导入*print(dir())#已定义对象的列表,包含“var”但不包含“_var”打印(var)#varprint(_var)#name错误:未定义名称“_var”_:单下划线是前导单下划线变量的特殊情况。按照惯例,它被用作垃圾变量,以存储一个不打算稍后访问的值。它也不会通过通配符导入来导入。这个for循环打印“我不能在课堂上讲话”10次,并且永远不需要访问_变量。对于范围(10)内的_:打印(“我不能在课堂上讲话”)var_:单尾随下划线变量。它们是传统的变量,用于避免与Python关键字冲突。如:class_=“MyClassName”__var:双前导下划线变量(至少两个前导下划线,最多一个尾随下划线)。当用作类属性(变量和方法)时,这些变量会受到名称篡改:在类之外,python会将属性重命名为_<class_name>__<attribute_name>。例子:类MyClass:__an_attribute=“属性值”my_class=我的类()print(my_class.MyClass__an_attribute)#“属性值”print(my_class.__an_attribute)#AttributeError:“MyClass”对象没有属性“__an_aattribute”当用作类外部的变量时,它们的行为类似于单前导下划线变量。__var__:双前导和尾随下划线变量(至少两个前导和尾随底线)。也称为dunders。python使用此命名约定在内部定义变量。避免使用此约定来防止python更新可能产生的名称冲突。Dunder变量的行为类似于单前导下划线变量:它们在类中使用时不受名称篡改的影响,但不会在通配符导入中导入。


为了用简单的语言来描述它,让我们将python变量的可访问性约定与Java中的访问修饰符进行比较:

(Python)                                     =   (Java)
_single_underscore_variable                  =   Protected (Accessible to class and its subclasses)
__double_underscore_variable                 =   Private (Accessible to class itself only)
no_underscore_variable                       =   Public (Accessible anywhere)

参考:https://www.tutorialsteacher.com/python/public-private-protected-modifiers