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


我想一劳永逸地解决这个问题。有人可以解释在Python中对象名称之前有前导下划线的确切含义吗?还解释了单个和双重前导下划线之间的区别。此外,无论相关对象是变量,函数,方法等,这个含义是否保持不变?


985
2017-08-19 17:15


起源


来自另一个主题的简短回答: stackoverflow.com/a/8689983/911945 - Anton Tarasenko


答案:


单下划线

具有前导下划线的类中的名称只是向其他程序员指示该属性或方法是私有的。但是,名称本身并没有什么特别之处。

去引用 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!'}

921
2017-08-19 17:52



如果使用2个下划线声明的变量名称不在类中怎么办?这只是一个正常的变量呢? - Dhruv Ramani
简单的含义是什么? __ 双下划线作为变量名?喜欢 a, __ = foo() - Clayton
这个答案极具误导性,因为它引导读者相信dunderscore用于使实例属性“超级私有”。这是 不 如此 在这里解释 作者雷蒙德·海廷格(Raymond Hettinger)明确指出,dunderscore不正确地用来标记私人成员,而它被设计成与私人成员相反。 - Markus Meskanen
@MarkusMeskanen我不同意,答案明确指出使用dunderscore来创建类私有变量和方法的实例。虽然dunderscore旨在使这些方法和变量容易被子类覆盖(使它们公开),但使用dunderscore会保留私有实例以便在该类中使用。 - arewm
@MarkusMeskanen:自由是指子类使用与超类相同的名称而不破坏超类 - 换句话说,超类的dunder名称变为私有。 - Ethan Furman


到目前为止优秀的答案,但一些花絮丢失。单个前导下划线并不完全正确 只是 惯例:如果你使用 from foobar import *和模块 foobar 没有定义 __all__ list,从模块导入的名称 不要 包括带有前导下划线的那些。让我们说吧 大多 一个惯例,因为这个案子是一个相当模糊的角落;-)。

领先 - 下划线惯例不仅被广泛使用 私人的 名称,但也适用于C ++所称的内容 保护 ones - 例如,完全打算被子类覆盖的方法的名称(甚至是那些  要在基类中重写它们 raise NotImplementedError! - )通常是单引导下划线名称,用于指示代码 运用 该类(或子类)的实例,表示方法不是直接调用的。

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


268
2017-08-19 17:21



那么你如何决定是否使用 _var_name 或使用 var_name +从中排除 __all__? - endolith
@endolith使用前导下划线向读者发出他们可能不应该使用它的信号(例如,因为您可能在2.0版本中更改它,甚至1.1版本);使用显式 __all__ 无论何时你想制作模块 from spam import * 友好(包括在互动口译员)。所以大多数时候,答案是 都。 - abarnert


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

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

__foo:这有实际意义:解释器用这个名字替换 _classname__foo 作为确保名称不会与另一个类中的类似名称重叠的方法。

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

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


245
2017-08-19 17:17



刚碰到过 __foo 并且很好奇。它如何与其他类的类似方法名称重叠?我的意思是你仍然需要访问它 instance.__foo()(如果它没有被翻译重命名),对吧? - Bibhas Debnath
这家伙 说明 from module import * 不导入下划线前缀对象。因此, _foo 不仅仅是一个惯例。 - dotancohen
@Bibhas:如果上课 B 子类类 A,并实施 foo(), 然后 B.foo() 覆盖了 .foo() 继承自 A。一个例子 B 只能访问 B.foo(),除了通过 super(B).foo()。 - naught101


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


176



谁今天学到了新东西?我。 - coderatchet
你的回答帮助我理解了名字错误。谢谢。 - Larynx
但是如果“__a”是一个类变量,那么即使使用python docs中的指令也无法访问它。 - Vitaliy Terziev


开头的单下划线:

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)的代码片段。这意味着错误是一个属性,它是模块的一部分,但是这个属性调用的方法_get_errors是“私有的”,所以你不应该访问它。

开头有两个下划线:

这引起了很多困惑。它不应该用于创建私有方法。它应该用于避免您的方法被子类覆盖或意外访问。我们来看一个例子:

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

    def test(self):
        self.__test()

a = A()
a.test()

输出:

$ python test.py
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()方法。但事实上,这是__的正确行为。因此,当您创建以__开头的方法时,这意味着您不希望任何人能够覆盖它,只能从自己的类内部访问它。

开头和结尾两个下划线:

当我们看到像这样的方法时 __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指南 会有所帮助。

请在python中找到更多魔术方法。 https://github.com/RafeKettler/magicmethods/blob/master/magicmethods.pdf


86





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

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

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

from sphinx.locale import l_, _

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


15





Python中的下划线(_)

以下是在Python中使用_的不同位置:

单下划线:

  • 在口译员
  • 一个名字后
  • 在一个名字之前

双下划线:

  • __leading_double_underscore

  • before_after

  • 单下划线

在口译员:

_返回Python REPL中最后执行的表达式值的值

>>> a = 10
>>> b = 10
>>> _
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_' is not defined
>>> a+b
20
>>> _
20
>>> _ * 2
40
>>> _
40
>>> _ / 2
20

忽略值:

多次我们不希望那时的返回值将这些值分配给wnderscore。它用作一次性变量。

# Ignore a value of specific location/index
for _ in rang(10)
    print "Test"

# Ignore a value when unpacking
a,b,_,_ = my_method(var1)

一个名字后

Python有默认关键字,我们不能将其用作变量名。为了避免python关键字和变量之间的这种冲突,我们在名称后使用下划线

例:

>>> class MyClass():
...     def __init__(self):
...             print "OWK"

>>> def my_defination(var1 = 1, class_ = MyClass):
...     print var1
...     print class_

>>> my_defination()
1
__main__.MyClass
>>>

在一个名字之前

变量/函数/方法名称之前的前导下划线向程序员指示它仅供内部使用,可以在类需要时修改。

这里,下划线的名称前缀被视为非公开。 如果从Import *指定,则所有以_开头的名称都不会导入。

Python没有指定真正的私有,因此如果在其中指定,则可以直接从其他模块调用 所有,我们也称它为弱私人

class Prefix:
...     def __init__(self):
...             self.public = 10
...             self._private = 12
>>> test = Prefix()
>>> test.public
10
>>> test._private
12
Python class_file.py

def public_api():
    print "public api"

def _private_api():
    print "private api"

从REPL调用文件

>>> from class_file import *
>>> public_api()
public api

>>> _private_api()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_private_api' is not defined

>>> import class_file
>>> class_file.public_api()
public api
>>> class_file._private_api()
private api
Double Underscore(__)

__leading_double_underscore

前导双下划线告诉python解释器重写名称以避免子类中的冲突.Interpreter使用类扩展名更改变量名称,并将该功能称为Mangling。 testFile.py

class Myclass():
    def __init__(self):
        self.__variable = 10

从REPL打电话

>>> import testFile
>>> obj = testFile.Myclass()
>>> obj.__variable
Traceback (most recent call last):
File "", line 1, in
AttributeError: Myclass instance has no attribute '__variable'
nce has no attribute 'Myclass'
>>> obj._Myclass__variable
10

在Mangling python解释器中用___修改变量名。所以多次使用它作为私有成员,因为另一个类不能直接访问该变量。 __的主要目的是仅在类中使用变量/方法如果要在类之外使用它,可以使公共API

class Myclass():
    def __init__(self):
        self.__variable = 10
    def func(self)
        print self.__variable

从REPL打电话

>>> import testFile
>>> obj = testFile.Myclass()
>>> obj.func()
10

__BEFORE_AFTER__

以__开头且以相同结尾的名称考虑Python中的特殊方法。 Python提供此方法以根据用户使用它作为运算符重载。

Python提供此约定来区分用户定义的函数和模块的函数

class Myclass():
    def __add__(self,a,b):
        print a*b

从REPL打电话

>>> import testFile
>>> obj = testFile.Myclass()
>>> obj.__add__(1,2)
2
>>> obj.__add__(5,2)
10

参考


10



加5和2得到10? - Valmond