单下划线和双下划线在对象名称前的含义是什么?

1816

在Python中,对象名称之前的单下划线和双下划线分别代表什么意思?


4
另一篇帖子中有一个很好的简短回答:https://dev59.com/Emoy5IYBdhLWcg3wPLn0#8689983 - Anton Tarasenko
请参阅此处的 优秀 回答,其中讨论了单个和双重前导下划线,并演示了 "名称修饰"!:Python 中的私有成员 - Gabriel Staples
18个回答

1585

单下划线

在一个类中,以单下划线作为前缀的名称表明属性或方法仅供该类内部使用,但并不会以任何方式强制使其私有化。 在模块中,将函数名前加上单下划线表示该函数不应从其他地方导入。

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

29
如果有一个在类中没有声明的变量名以双下划线开头,那么它只是一个普通变量,对吗? - Dhruv Ramani
170
这个答案极具误导性,因为它让读者相信dunderscore用于使实例属性“超级私有”。这是不正确的,正如Raymond Hettinger在这里所解释的那样,他明确指出,dunderscore被错误地用于标记成员为私有,而它的设计目的是相反的。 - Markus Meskanen
28
@MarkusMeskanen 我不同意,答案明确说明了使用双下划线来制作类私有变量和方法,虽然双下划线的设计是为了使这些方法和变量易于被子类覆盖(使它们公开),但使用双下划线保留了一个私有实例供该类内部使用。 - arewm
14
自由是让子类使用与超类相同的名称,而不会破坏超类——换句话说,超类的双下划线名称成为了私有的。 - Ethan Furman
17
对于单个下划线,答案表示“名称本身没有做任何特殊处理”,但接着说 from M import * 处理方式不同...因此确实进行了某些特殊处理... - flow2k
显示剩余6条评论

531
  • _foo: 这只是一种约定。程序员用这种方式来表明变量是私有的(在Python中意味着什么,由程序员自己定义)。

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

  • __foo__: 这也只是一种约定。 Python系统使用这种方式避免名称与用户名称冲突。

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


9
刚刚发现了 __foo,很好奇。它如何与其他类中的类似方法名称重叠?我的意思是,你仍然需要像访问 instance.__foo() 一样访问它(如果解释器没有将其重命名),对吗? - Bibhas Debnath
174
这位作者声称,from module import * 不会导入以下划线开头的对象。因此, _foo 不仅是一种约定。 - dotancohen
7
如果类B继承自类A,并且两者都实现了foo()方法,则B.foo()会覆盖从A继承的.foo()方法。B类的实例只能访问B.foo()方法,除非通过 super(B).foo() 方法。 - naught101
11
对于__dunder__(双下划线)名称,隐式调用会跳过实例字典,因此在某些情况下这可能不仅仅是一种命名约定(请参见数据模型中的special method lookup部分)。 - wim

380
迄今为止的回答都很好,但缺少一些细节。单个下划线开头不仅仅是惯例:如果你使用from foobar import *,并且模块foobar没有定义一个__all__列表,则从该模块导入的名称不包括下划线开头的名称。可以说这主要是一种惯例,因为这种情况是一个相当晦涩的角落;-)。
在Python中,下划线开头的约定不仅仅用于私有名称,也用于C++所称的受保护名称——例如,那些完全打算被子类重写的方法的名称(即使是那些在基类中必须重写,因为它们会raise NotImplementedError!)通常是单个下划线的名称,以向使用该类(或子类)实例的代码表明,这些方法不应直接被调用。
例如,要创建一个具有不同排队规则的线程安全队列,可以导入Queue,子类化Queue.Queue,并覆盖_get_put等方法;“客户端代码”永远不会调用那些(“挂钩”)方法,而是调用(“组织”)公共方法,如putget(这被称为模板方法设计模式——例如,可以在此处找到一个基于我对该主题的演讲视频的有趣演示,其中包括摘要)。

编辑:讲座描述中的视频链接现在已经失效了。你可以在这里这里找到前两个视频。


2
那么你如何决定是使用 _var_name 还是使用 var_name 并将其从 __all__ 中排除? - endolith
3
使用前导下划线来提示代码读者他们可能不应该使用它(例如,因为您可能会在版本2.0或甚至1.1中更改它);每当您想要使模块“from spam import *”友好(包括在交互式解释器中)时,请使用显式的__all__。因此,大多数情况下,答案是“两者都需要”。 - abarnert
@AlexMartelli 为什么它不能导入它们?我的意思是,哪些进程阻止了它们的导入? - Vicrobot
2
我喜欢C++的比喻。首先,当人们称_private时,我不喜欢它。显然,我在谈论比喻,因为在Python中没有真正的private。当深入语义时,我会说我们可以将_与Java的protected联系起来,因为在Java中,protected表示“派生类和/或同一包内”。将包替换为模块,因为PEP8已经告诉我们,在讨论*导入时,_不仅仅是一个约定,就这样。当谈到类内标识符时,__肯定相当于Java的private - Marius Mucenicu
5
虽然这是一个还不错的回答,但它也有很强的自我推销色彩。 - Hybrid web dev
显示剩余6条评论

257

._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而不是仅仅使用双下划线名称,您可以访问隐藏的值。


但是如果“__a”是一个类变量,那么即使按照Python文档的说明也无法访问它。 - Vitaliy Terziev
请问您能否更新您的答案,提供一个关于双下划线与继承相关的示例? - variable
1
根据之前的帖子和 PEP-8,._variable 不仅是一种约定:"from M import *" 不会导入以下划线开头的对象。然而,在所呈现的情况下,将其显示为类属性并不会改变任何内容。 - pdaawr
“.__variable经常被错误地认为是超级私有的”,这有点误导人。双下划线虽然会进行名称修饰,但其实是为了使其真正成为私有变量 - Amorphous

168

在方法名或属性名前加单下划线:

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

正如我们所看到的,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指南。如需更多魔术方法,请参阅此PDF文件


2
在我自己编辑过这个答案之后,我更喜欢 https://dev59.com/Emoy5IYBdhLWcg3wPLn0#8689983。 - Josiah Yoder
“正如我们所看到的,A.test()没有调用B.__test()方法”,你在哪里调用了A.test()? - variable
@variable 他的意思是B.test(),我已经纠正了! - adkl

95

根据Python中的下划线含义

  • 单个前导下划线(_var): 命名约定表明该名称仅供内部使用。通常不由Python解释器执行(除了通配符导入),仅作为对程序员的提示。
  • 单个尾随下划线(var_): 按惯例用于避免与Python关键字冲突的命名。
  • 双前导下划线(__var): 在类上下文中使用时触发名称混淆。由Python解释器强制执行。
  • 双前导和尾随下划线(__var__): 表示Python语言定义的特殊方法。请避免使用此命名方案来命名自己的属性。
  • 单个下划线(_): 有时用作临时或无关紧要变量的名称("不关心")。还可以用作PythonREPL中最后一个表达式的结果。

20

由于很多人都在参考雷蒙德的演讲,因此我将把他说的话简单写下来:

The intention of the double underscores was not about privacy. The intention was to use it exactly like this

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

It's actually the opposite of privacy, it's all about freedom. It makes your subclasses free to override any one method without breaking the others.

假设你在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


1
谢谢,幻灯片没有正确显示,所以我最终没能理解为什么我的代码会失败。 - cgte
我得到了相同的答案,无论周长是否有前导下划线。 - Gringo Suave

20
有时你会遇到一个看起来像元组的变量,但前面带有下划线,就像这样:
def foo(bar):
    return _('my_' + bar)
在这种情况下,_()是一个本地化函数的别名,它会根据语言环境将文本转换为适当的语言等。例如,Sphinx就是这样做的,在导入中可以找到该函数。
from sphinx.locale import l_, _

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


1
是的,这个答案很有帮助;gettext模块描述了这种模式:“gettext通过在翻译数据库中查找字面字符串并提取相应的翻译字符串来工作。通常的模式是将适当的查找函数绑定到名称“_”(一个下划线字符),以便代码不会被大量调用具有较长名称的函数所淹没。”也许这就是sphinx在“幕后”所做的事情;类似于示例t = gettext.translation('example_domain', 'locale', fallback=True); _ = t.gettext - Nate Anderson
1
我在我的项目中找不到_本地化函数的导入或创建。我意识到我的项目使用了gettext.install(...),它会"将函数 _() 安装在Python的内置命名空间中...",这是gettext模块的"基于类的API"的一种技术(与直接使用API gettext.gettext(...)相比)。关于内置命名空间和模块全局命名空间的更多信息,请参阅此处 - Nate Anderson

17

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提出了一个有些不同的问题,但既然我找到了另一个关于“如何设置私有变量”的重复问题,我想在这里添加一些额外的信息。


只是补充一下:使用propeety作为方法装饰器会更加简洁。 - TheLizzard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接