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

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

659156 次浏览

单下划线

在类中,带有前导下划线的名称向其他程序员表明该属性或方法旨在在该类中使用。但是,隐私无论如何都不是强制。对模块中的函数使用前导下划线表示它不应该从其他地方导入。

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.__superprivateTraceback (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名称在模块中不会出现名称损坏,如果从模块中导入所有名称(从模块导入*),则不会导入以一个(或多个)下划线开头的名称,也不会导入help(模块)中显示的名称。

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

前导下划线约定不仅广泛用于私人名称,而且还用于C++称之为保护的名称-例如,完全打算被子类覆盖的方法的名称(即使是被覆盖的方法,因为在基类中它们raise NotImplementedError!-)通常是单前导下划线名称,以指示该类(或子类)的使用实例,这些方法不打算直接调用。

例如,要创建一个具有不同于FIFO的排队规则的线程安全队列,需要导入Queue、Queue. Queue子类,并覆盖_get_put等方法;“客户端代码”从不调用那些(“钩子”)方法,而是调用(“组织”)公共方法,如putget(这被称为模板方法设计模式——例如,参见这里,这是一个基于我关于该主题的演讲视频的有趣演示,并添加了成绩单的概要)。

编辑:演讲描述中的视频链接现已中断。您可以找到前两个视频这里这里

有时你有一个似乎是带有前导下划线的元组,如

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

在这种情况下,_()是本地化函数的别名,该函数根据语言环境对文本进行操作以将其放入适当的语言等。例如,Sphinx就是这样做的,你会在导入中找到

from sphinx.locale import l_, _

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

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

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

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

如果您迫切需要,您仍然可以访问.__mangled变量。双下划线只是命名或重命名变量,类似于instance._className__mangled

示例:

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

_b是可访问的,因为它只被惯例隐藏

>>> t.__aTraceback (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而不仅仅是双下划线名称,您可以访问隐藏值

如果一个人真的想让一个变量只读,恕我直言,最好的方法是使用属性(),只传递给它一个getter。使用属性(),我们可以完全控制数据。

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
@propertydef default(self):return self.__default
@default.setterdef 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 showchild_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/form/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 AttributeErrora._A__test() # Works! We can access the mangled name directly!

输出:

$ python test.pyI'm test method in class AI'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.pyI'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 + 5060

总有一个运算符或本机函数调用这些神奇的方法。有时它只是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      # 10print number - 20      # 40

有关更多详细信息,请参阅PEP-8指南。有关更多魔术方法,请参阅本pdf

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

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

获取_和__的事实很容易;其他答案表达得很好。用法更难确定。

我是这样看的:

_

应该用于指示函数不适合公共使用,例如API。这和导入限制使它的行为很像c#中的internal

__

应该用于避免继承等级制度中的名称冲突和避免延迟绑定。就像c#中的私有一样。

==>

如果你想表示某物不供公众使用,但它应该像protected一样使用_。如果你想表明某物不供公众使用,但它应该像private一样使用__

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

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

但问题是,在我看来,如果在重写方法时没有IDE警告您,如果您意外地重写了基类中的方法,则可能需要一段时间才能找到错误。

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

双下划线的用意与隐私无关。用意是完全像这样使用它

class Circle(object):
def __init__(self, radius):self.radius = radius
def area(self):p = self.__perimeter()r = p / math.pi / 2.0return 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

这实际上与隐私相反,它完全是关于自由的。它使您的子类可以自由地覆盖任何一种方法而不会破坏其他方法

假设你没有在Circle中保留perimeter的本地引用。现在,派生类Tire覆盖了perimeter的实现,而没有触及area。当你调用Tire(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

我提供了简单的例子和简单的定义/含义。

含义:

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)

根据Python中下划线的含义

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

在方法的情况下,您可以使用双下划线来隐藏具有以下模式的私有“方法”:

# Private methods of MyClassdef _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>' is not defined错误时,我偶然发现了这一点。

  • _var:在python中带有前导下划线的变量是经典变量,旨在通知使用您的代码的其他人该变量应保留供内部使用。它们与经典变量有一点不同:在定义它们的对象/模块时进行通配符导入时不会导入它们(定义#1变量时有例外)。例如:

    # foo.py
    var = "var"_var = "_var"
    # bar.py
    from foo import *
    print(dir())  # list of defined objects, contains 'var' but not '_var'print(var)    # varprint(_var)   # NameError: name '_var' is not defined
  • _:单下划线是前导单下划线变量的特例。它被约定用作垃圾变量,用于存储不打算稍后访问的值。它也不会被通配符导入。例如:这个for循环打印“我必须在课堂上不说话”10次,并且永远不需要访问_变量。

    for _ in range(10):print("I must not talk in class")
  • var_:单个尾随下划线变量。它们是约定用于避免与Python关键字冲突的经典变量。例如:

    class_ = "MyClassName"
  • __var:双前导下划线变量(至少两个前导下划线,最多一个尾随下划线)。当用作类属性(变量和方法)时,这些变量会受到名称修饰:在类之外,python会将属性重命名为_<Class_name>__<attribute_name>。示例:

    class MyClass:__an_attribute = "attribute_value"
    my_class = MyClass()print(my_class._MyClass__an_attribute)  # "attribute_value"print(my_class.__an_attribute)  # AttributeError: 'MyClass' object has no attribute '__an_attribute'

    当用作类之外的变量时,它们的行为就像单个前导下划线变量。

  • __var__:双前导和尾随下划线变量(至少两个前导和尾随下划线)。也称为扣篮。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