对象名称前的单下划线和双下划线是什么意思?


Answers:


1152

单下划线

类中带有下划线的名称仅是为了向其他程序员表明该属性或方法旨在私有。但是,名称本身并没有做任何特别的事情。

引用PEP-8

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

双下划线(名称改写)

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

17
如果有一个变量名声明有2个下划线而不在类中怎么办?那只是一个普通变量,对吧?
Dhruv Ramani 2015年

5
简单地将__双下划线作为变量名是什么意思?像a, __ = foo()
AJ

104
这个答案极具误导性,因为它使读者相信dunderscore用于使实例属性“超私有”。这是没有的情况下,如这里解释的雷蒙德赫廷杰,谁明确指出dunderscore被incorrrectly用来标记部件私人,虽然它被设计为私人相反。
马库斯·梅斯卡宁

13
@MarkusMeskanen我不同意,答案明确指出要使用dunderscore来制作类私有变量和方法的实例。尽管dunderscore旨在使这些方法和变量易于被子类覆盖(将它们公开),但使用dunderscore保留了在该类中使用的私有实例。
arewm '16

6
@MarkusMeskanen:自由是子类可以使用与超类相同的名称,而不会破坏超类-换句话说,超类的笨拙名称对其自身变为私有。
伊桑·弗曼

311

到目前为止,很好的答案,但缺少一些花絮。单个前导下划线并不仅仅是一个约定:如果使用from foobar import *,并且模块foobar未定义__all__列表,则从模块导入的名称将包括带有下划线的名称。可以说这主要是一个约定,因为这种情况是一个相当模糊的角落;-)。

领先的下划线惯例被广泛用于不只是私人的名字,而且对什么C ++称之为保护的-例如,都完全的方法名称由子类(甚至是那些覆盖被否决,因为在它们的基类raise NotImplementedError!-)通常是单引号下划线名称,用于指示使用该类(或子类)的实例进行编码时,这些方法并不旨在被直接调用。

例如,要创建一个与FIFO不同的排队规则的线程安全队列,可以导入Queue,将Queue.Queue子类化,并覆盖诸如_get_put;之类的方法。“客户端代码”从不调用那些(“挂钩”)方法,而是调用(和“组织”)公共方法,例如putget(称为模板方法设计模式),例如,有关基于视频的有趣演示,请参见此处。我关于这个话题的演讲,以及抄本的提要)。

编辑:演讲说明中的视频链接现在已断开。您可以在此处此处找到前两个视频。


1
那么,您如何决定使用_var_name还是使用var_name+从中排除呢__all__
Endlith '17

3
@endolith使用前导下划线告知您的代码读者他们可能不应该使用此代码(例如,因为您可能会在2.0版甚至1.1版中进行更改);__all__每当您想使模块from spam import *友好时(包括在交互式解释器处),请使用显式。因此,在大多数情况下,答案是两者兼而有之
abarnert

@AlexMartelli是否在文档中或其他地方合法讨论了与导入相关的规则?
Vicrobot '18年

1
我喜欢C ++类比。首先,当人们称呼_ 私人时,我不喜欢它。显然,我在谈论类比,因为Python中没有什么是真正私有的。当深入到语义我想说,我们可以绑_到Java的保护,因为proctected在Java中的意思是“派生类和/或在相同的包”。用模块替换软件包,因为PEP8已经告诉我们_在谈论*导入时这不仅仅是一个约定,而且您已经有了。当谈论一个类中的标识符时,这绝对__等同于Java的私有
Marius Mucenicu

2
虽然这是一个不错的答案,但它也是自我推广。
混合式网络开发人员

298

__foo__:这只是一个约定,这是Python系统使用不会与用户名冲突的名称的一种方式。

_foo:这只是一个约定,是程序员用来表明变量是私有的(无论在Python中是什么意思)的一种方式。

__foo:这具有实际含义:解释器将其替换为,_classname__foo以确保该名称不会与另一个类中的相似名称重叠。

在Python世界中,没有其他形式的下划线具有意义。

在这些约定中,类,变量,全局等之间没有区别。


6
只是偶然__foo而好奇。它如何与其他类的相似方法名称重叠?我的意思是您仍然必须以类似的方式访问它instance.__foo()(如果未由解释程序重命名),对吗?
Bibhas Debnath

81
此人声明from module import *不会导入下划线前缀的对象。因此,_foo不仅仅是一个约定。
dotancohen

4
@Bibhas:如果class B子类class A,并且都实现foo(),则B.foo()覆盖从.foo()继承A。的实例B只能通过进行访问B.foo(),除非通过super(B).foo()
naught101

3
对于__dunder__名称,隐式调用会跳过实例字典,因此在某些情况下,它可能不仅仅是命名约定(请参见数据模型中的特殊方法查找部分)。
维姆

205

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

.__variable通常被错误地认为是超私有的,而其实际含义只是为了防止意外访问而使用 namemangle [1]

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

您仍然可以.__mangled根据需要访问变量。双下划线只是将变量命名或重命名为类似instance._className__mangled

例:

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'

通过访问instance._className__variable而不只是双下划线名称,您可以访问隐藏值


但怎么样,如果“__A”是一个类变量,那么你甚至不能从python文档..指令访问
维塔利Terziev

请您举例说明关于继承的双下划线吗?
变量

115

开头下划线:

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)。在这段代码中,它errors是一个公共属性,但是此属性调用的方法_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

如我们所见,A.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的一个钩子调用。例如__init__()在创建对象之后创建对象时__new__()调用,以构建实例...

让我们举个例子...

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指南。有关更多魔术方法,请参见此PDF


1
我自己编辑

您所说的“如我们所见,A.test()没有调用B .__ test()方法”是什么意思-您在哪里调用了A.test()?
变量

18

有时您看起来像是一个带下划线的元组,如下所示

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

在这种情况下,正在发生的情况是_()是本地化功能的别名,该功能基于文本在文本上进行操作以将其放入适当的语言等。例如,Sphinx执行此操作,您将在导入文件中找到

from sphinx.locale import l_, _

在sphinx.locale中,_()被分配为某些本地化功能的别名。


9

既然有这么多人在谈论雷蒙德的演讲,我将写下他的话简化一下:

双下划线的目的不是关于隐私。目的是像这样完全使用它

class Circle(object):

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

    def area(self):
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi * r ** 2.0

    def perimeter(self):
        return 2.0 * math.pi * self.radius

    __perimeter = perimeter  # local reference


class Tire(Circle):

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

实际上,这是隐私的对立面,它与自由有关。它使您的子类可以随意覆盖任何一种方法而不会破坏其他方法

假设您没有保留perimeterin 的本地引用Circle。现在,派生类将Tire覆盖实现perimeter,而无需触摸areaTire(5).area()从理论上讲,当您调用时,它仍Circle.perimeter应用于计算,但实际上,它正在使用Tire.perimeter,这不是预期的行为。这就是为什么我们在Circle中需要本地引用。

但是为什么要__perimeter代替_perimeter呢?因为_perimeter仍然给派生类重写的机会:

class Tire(Circle):

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

    _perimeter = perimeter

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

如果您的类不会被继承,或者方法重写不会破坏任何内容,那么您根本不需要__double_leading_underscore


谢谢,幻灯片没有正确显示,所以我最终不去担心为什么我的代码会失败。
cgte19

8

如果您真的想将变量设为只读,恕我直言,最好的方法是使用仅传递有getter的property()。使用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提出了一个稍有不同的问题,但是由于我发现了另一个问题,询问“如何设置私有变量”与此标记为重复,因此我想在此处添加此附加信息。


6

进入https://dbader.org/blog/含义-underscores-in-python

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

4

单个下划线是惯例。名称是否以单个下划线开头,与解释器的观点没有区别。

双开头和结尾的下划线用于内置的方法,如__init____bool__

双下划线开头W / O尾随同行是惯例也是如此,但是,该类方法将被错位的解释。对于变量或基本函数名称,没有区别。


4

好的答案都正确。我提供了简单的示例以及简单的定义/含义。

含义:

some_variable--►任何人都可以看到这是公开的。

_some_variable--►任何人都可以看到这是公开的,但这是表示私有的惯例... 警告 Python不会执行任何强制执行。

__some_varaible--►Python将变量名替换为_classname__some_varaible(又名名称改写),它降低/隐藏了它的可见性,更像是私有变量。

坦白地说,根据Python文档

“只能从对象内部访问的“私有”实例变量在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)

_ _some_varaible - .... 和它减少/隐藏它的知名度和更喜欢私有变量。不,名称修饰是重点,它不会隐藏方法。
AMC

3

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

但是,例如,__double_underscore名称在模块中没有名称混杂。发生的情况是,如果您从模块(从module import *)全部导入,则不会导入以一个(或多个)下划线开头的名称,也不会在help(module)中显示名称。


1
此外,以一个或多个下划线开头且具有两个或多个结尾下划线的名称再次表现为其他名称。
Bentley4年

3

这是一个关于双下划线属性如何影响继承的类的简单说明性示例。因此,使用以下设置:

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'

这对于某些人可能是显而易见的,但是在更复杂的环境中使我措手不及


3

Python中不存在只能从对象内部访问的“私有”实例变量。但是,大多数Python代码遵循一个约定:以下划线开头的名称(例如_spam)应被视为API的非公开部分(无论是函数,方法还是数据成员) 。它应被视为实现细节,如有更改,恕不另行通知。

参考 https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references


1
_与例如c#中的internal非常相似,然后类似于private。我要说的是,双下划线与私有更像,下划线与私有更像。
燕丽

2

得到_和__的事实非常容易。其他答案很好地表达了他们。使用情况很难确定。

这是我的看法:

_

应该用于表示某个功能不公开使用,例如API。这和导入限制使它的行为很像internal在c#中。

__

应该用来避免继承层次结构中的名称冲突以及避免后期绑定。就像C#中的private。

==>

如果您想指出某事不是公共用途,而是应该像protecteduse 一样_。如果您想指出某事不是公共用途,而是应该像privateuse 一样__

这也是我非常喜欢的报价:

问题在于,类的作者可能会合理地认为“此属性/方法名称应为私有的,只能从该类定义中访问”,并使用__private约定。但是稍后,该类的用户可能会创建合法需要访问该名称的子类。因此,要么必须修改超类(可能很难或不可能),要么子类代码必须使用手动修改的名称(充其量是丑陋和脆弱的)。

但是我的问题是,如果没有IDE在您重写方法时警告您,如果您意外地从基类中重写了一个方法,则发现错误可能会花费您一段时间。

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.