1591

有人可以解释在 Python 中对象名称前有单下划线和双下划线的确切含义,以及两者之间的区别吗?

此外,无论所讨论的对象是变量、函数、方法等,其含义是否保持不变?

4

17 回答 17

1395

单下划线

在一个类中,带有前导下划线的名称只是为了向其他程序员表明该属性或方法是私有的。但是,名称本身并没有什么特别之处。

引用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!'}
于 2009-08-19T17:15:53.427 回答
395

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

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

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

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

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

于 2009-08-19T17:21:29.873 回答
355

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

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

例如,要创建一个具有与 FIFO 不同的排队规则的线程安全队列,可以导入 Queue、子类 Queue.Queue,并覆盖诸如和之类的_get方法_put;“客户端代码”从不调用那些(“钩子”)方法,而是调用(“组织”)公共方法,例如putget(这被称为模板方法设计模式 - 请参见此处以获取基于视频的有趣演示我关于这个主题的一次谈话,加上成绩单的概要)。

编辑:会谈描述中的视频链接现已损坏。您可以在此处此处找到前两个视频。

于 2009-08-19T17:52:36.190 回答
233

._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'

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而不只是双下划线名称,您可以访问隐藏的值

于 2012-09-27T20:56:44.287 回答
144

开头的单下划线:

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

于 2014-12-15T10:10:20.990 回答
55

根据Python中下划线的含义

  • 单前导下划线(_var:表示名称的命名约定是供内部使用的。通常不由 Python 解释器强制执行(通配符导入除外),仅作为对程序员的提示。
  • Single Trailing Underscore( var_):按惯例使用以避免与 Python 关键字的命名冲突。
  • 双前导下划线(__var:在类上下文中使用时触发名称修改。由 Python 解释器强制执行。
  • Double Lead and Trailing Underscore( __var__):表示 Python 语言定义的特殊方法。避免为您自己的属性使用这种命名方案。
  • Single Underscore( _):有时用作临时或无关紧要的变量的名称(“不关心”)。另外:Python REPL中最后一个表达式的结果。
于 2019-11-27T08:45:06.263 回答
19

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

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

在这种情况下,发生的事情是 _() 是本地化函数的别名,该函数对文本进行操作以根据语言环境将其转换为正确的语言等。例如,Sphinx 执行此操作,您会在导入中找到

from sphinx.locale import l_, _

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

于 2012-01-11T16:28:22.907 回答
16

既然很多人都在指雷蒙德的演讲,我将通过写下他所说的话来使它更容易一点:

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

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,而不涉及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.

于 2018-10-20T08:10:44.960 回答
8

如果真的想将变量设为只读,恕我直言,最好的方法是使用 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 提出了一个不同的问题,但由于我发现另一个问题要求“如何设置私有变量”与这个问题重复,所以我想在这里添加这个附加信息。

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

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

    for _ in range(10):
        print("I must not talk in class")
    
  • __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__: 双前导和尾随下划线变量(至少两个前导和尾随下划线)。也称为dunders。python 使用此命名约定在内部定义变量。避免使用此约定来防止 python 更新可能出现的名称冲突。Dunder 变量的行为类似于单个前导下划线变量:它们在类中使用时不受名称修饰的影响,但不会在通配符导入中导入。

于 2021-01-06T14:12:54.847 回答
6

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

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'

这对某些人来说可能很明显,但它让我在更复杂的环境中措手不及

于 2014-08-22T19:15:48.357 回答
6

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

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

没有尾随对应的双前导下划线也是一种约定,但是,类方法将被解释器破坏。对于变量或基本函数名称没有区别。

于 2009-08-19T17:17:11.707 回答
6

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

意义:

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

_some_variable --► 它是公开的,任何人都可以看到,但它是表示私有的约定……警告Python 不执行任何强制。

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

老实说这里根据 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)
于 2019-01-21T22:23:39.263 回答
3

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

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

于 2015-02-07T17:57:10.620 回答
3

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

但是 __double_underscore 名称在模块中不会被命名,例如。如果从一个模块中导入全部(从模块导入 *),则不会导入以一个(或多个)下划线开头的名称,也不会在 help(module) 中显示名称。

于 2009-08-19T17:31:04.347 回答
1

获取 _ 和 __ 的事实非常容易;其他答案很好地表达了它们。用途更难确定。

这就是我的看法:

_

应该用来表示一个函数不是供公共使用的,例如 API。这和导入限制使它的行为很像internal在 c# 中。

__

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

==>

如果您想表明某些东西不供公众使用,但它应该表现得像protecteduse _。如果您想表明某些东西不供公众使用,但它应该表现得像privateuse __

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

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

但在我看来,问题在于如果没有 IDE 在您覆盖方法时警告您,如果您不小心覆盖了基类中的方法,则发现错误可能需要一段时间。

于 2017-11-04T17:46:10.817 回答
0

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

# 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>' is not defined今天,当我尝试对类方法使用双下划线并得到错误时,我偶然发现了这一点。

于 2020-12-22T20:29:36.913 回答