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

Ram*_*hum 1205 python naming-conventions

有人可以解释在Python中对象名称之前有前导下划线的确切含义吗?另外,解释单个和双重前导下划线之间的区别.此外,无论所讨论的对象是变量,函数,方法等,这个含义是否保持不变?

And*_*ton 1083

单下划线

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

引用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!'}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案极具误导性,因为它引导读者相信dunderscore用于使实例属性"超级私有".这不是*的情况,正如[在此解释](https://www.youtube.com/watch?v=HTLu2DFOdTg&t=33m8s)由Raymond Hettinger所说,他明确指出dunderscore不正确地用来标记私人成员,而它被设计成与私人相反. (79认同)
  • 如果使用2个下划线声明的变量名称不在类中,该怎么办?这只是一个正常的变量呢? (14认同)
  • @MarkusMeskanen我不同意,答案明确指出使用dunderscore来创建类私有变量和方法的实例.虽然dunderscore旨在使这些方法和变量容易被子类覆盖(使其公开),但使用dunderscore会保留私有实例以在该类中使用. (8认同)
  • 简单的`__`双下划线作为变量名的含义是什么?比如`a,__ = foo()` (4认同)
  • @MarkusMeskanen:自由是指子类使用与超类相同的名称而不破坏超类 - 换句话说,超类的dunder名称变为私有. (4认同)
  • 说真的,我认为在私有变量之前使用 _ 是错误的。_ 太含糊了。这就是为什么它让很多人望而却步。更合适的约定是“privateVarName”,因为它毫无疑问地表明了预期的可见性应该是什么。我意识到这是额外的打字,但是,在编程方面,清晰度远比优雅重要。使用不言自明的变量名,不要让人们必须到 StackOverflow 来阅读你的代码。这是您的代码不可读的好迹象。 (2认同)
  • 对于单个下划线,答案说“名称本身并没有做任何特殊的事情”,但接着说“ from M import *”对它的处理方式有所不同...所以完成了一些特殊的*过程... (2认同)
  • 请您用关于继承的双下划线示例更新您的答案吗? (2认同)

Ale*_*lli 294

到目前为止优秀的答案,但一些花絮丢失.单个前导下划线并不仅仅是一个约定:如果使用from foobar import *,并且模块foobar未定义__all__列表,则从模块导入的名称包括具有前导下划线的名称.让我们说它主要是一种惯例,因为这个案例是一个非常模糊的角落;-).

前导下划线约定不仅被广泛用于私有名称,而且被广泛用于C++将其称为受保护的名称 - 例如,完全打算被子类覆盖的方法的名称(甚至是那些必须被覆盖的方法的名称)它们的基类raise NotImplementedError! - )通常是单引导下划线名称,用于指示代码使用该类(或子类)的实例,这些方法不是直接调用的.

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

  • @endolith使用前导下划线向读者发出他们可能不应该使用它的信号(例如,因为您可能在2.0版本中更改它,甚至1.1版本); 每当你想使模块`来自垃圾邮件导入*'友好时(包括在交互式解释器中),就使用显式的`__all__`.所以大部分时间,答案都是_both_. (3认同)
  • 虽然这是一个不错的答案,但它也是一种自我推销。 (3认同)
  • 那么你如何决定是使用`_var_name` 还是使用`var_name` + 从`__all__` 中排除它? (2认同)
  • 我喜欢 C++ 的类比。首先,我不喜欢人们称“_”为**私有**。显然我正在谈论类比,因为 Python 中没有什么是真正的**私有**。当深入研究语义时,我想说我们可以将“_”与 Java 的 **protected** 联系起来,因为 Java 中的 **protected** 意味着“派生类和/或在同一包内”。将 package 替换为 module,因为 PEP8 已经告诉我们,“_”不仅仅是谈论“*”导入时的约定,现在您已经有了它。当谈论类中的标识符时,毫无疑问 __` 等同于 Java 的 **private**。 (2认同)

Ned*_*der 281

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

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

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

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

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

  • [这个人](http://stackoverflow.com/questions/551038/private-implementation-class-in-python)声明`from module import*`不会导入下划线前缀对象.因此,`_foo`不仅仅是一个约定. (77认同)
  • 刚遇到`__foo`并好奇.它如何与其他类的类似方法名称重叠?我的意思是你仍然必须像`instance .__ foo()`那样访问它(如果它没有被解释器重命名),对吧? (5认同)
  • 对于“__dunder__”名称,隐式调用会跳过实例字典,因此在某些情况下它可能不仅仅是命名约定(请参阅[*特殊方法查找*](https://docs.python.org/3/reference /datamodel.html#special-method-lookup) 数据模型中的部分)。 (5认同)
  • @Bibhas:如果类`B`继承类`A`,并且都实现`foo()`,那么`B.foo()`会覆盖从`A`继承的`.foo()`.除了通过`super(B).foo()`之外,`B`的一个实例只能访问`B.foo()`. (4认同)

Nic*_*CSE 194

._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'
Run Code Online (Sandbox Code Playgroud)

t._b是可访问的,因为它只是按惯例隐藏的

>>> t.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'
Run Code Online (Sandbox Code Playgroud)

找不到t .__ a,因为它因名称错误而不再存在

>>> t._Test__a
'a'
Run Code Online (Sandbox Code Playgroud)

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

  • 根据上面的帖子和 PEP-8,`._variable` 不仅仅是一个约定:“`from M import *` 不会导入名称以下划线开头的对象。”。但是,在所呈现的将其显示为类属性的情况下,它不会改变任何内容。 (2认同)

Pyt*_*Dev 109

开头的单下划线:

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)
Run Code Online (Sandbox Code Playgroud)

(此代码段取自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!
Run Code Online (Sandbox Code Playgroud)

输出:

$ python test.py
I'm test method in class A
I'm test method in class A
Run Code Online (Sandbox Code Playgroud)

现在创建一个子类B并为__test方法进行自定义

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

b = B()
b.test()
Run Code Online (Sandbox Code Playgroud)

输出将是....

$ python test.py
I'm test method in class A
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,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
Run Code Online (Sandbox Code Playgroud)

总有一个操作符或本机函数调用这些魔术方法.有时它只是在特定情况下的钩子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
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅PEP-8指南.有关更多魔术方法,请参阅此PDF.

  • 自己编辑这个答案后,我更喜欢 /sf/answers/608298841/ (2认同)

Feu*_*uda 29

根据Python 中下划线的含义

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


Tim*_*m D 18

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

def foo(bar):
    return _('my_' + bar)
Run Code Online (Sandbox Code Playgroud)

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

from sphinx.locale import l_, _
Run Code Online (Sandbox Code Playgroud)

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


Dev*_*aha 7

如果一个人真的想让变量只读,恕我直言,最好的方法是使用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) 
Run Code Online (Sandbox Code Playgroud)

我知道OP提出了一个不同的问题但是因为我发现了另一个问题,询问"如何设置私有变量"标记重复这个问题,我想在这里添加这些额外的信息.


gre*_*pit 6

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

意义:

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

_some_variable --?它是公开的,任何人都可以看到,但这是表明私有的约定……警告Python 没有执行任何强制措施。

__some_variable --?Python 用 _classname__some_varaible (AKA name mangling) 替换变量名,它减少/隐藏它的可见性,更像是私有变量。

老实说,根据 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)
Run Code Online (Sandbox Code Playgroud)


Sil*_*ost 5

单引导下划线是一种惯例.如果名称是否以单个下划线开头,则与解释者的观点没有区别.

双前缘和后下划线用于内置的方法,如__init__,__bool__

不带尾随对应项的双引导下划线也是一种约定,但是,类方法将被解释器破坏.对于变量或基本函数名称,不存在差异.


Mar*_*arc 5

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

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"
Run Code Online (Sandbox Code Playgroud)

如果然后在 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'
Run Code Online (Sandbox Code Playgroud)

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


lai*_*e9m 5

既然有这么多人在谈论雷蒙德的演讲,我将写下他的话变得更容易一些:

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

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
Run Code Online (Sandbox Code Playgroud)

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

假设您没有保留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
Run Code Online (Sandbox Code Playgroud)

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

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


Mat*_*let 5

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

    # foo.py
    
    var = "var"
    _var = "_var"
    
    Run Code Online (Sandbox Code Playgroud)
    # 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
    
    Run Code Online (Sandbox Code Playgroud)
  • _:单下划线是前导单下划线变量的特例。按照惯例,它被用作垃圾变量,以存储以后不打算访问的值。它也不是通过通配符导入导入的。例如:这个for循环打印了 10 次“我不能在课堂上说话”,并且永远不需要访问_变量。

    for _ in range(10):
        print("I must not talk in class")
    
    Run Code Online (Sandbox Code Playgroud)
  • __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'
    
    Run Code Online (Sandbox Code Playgroud)

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

  • __var__: 双前导和尾随下划线变量(至少两个前导和尾随下划线)。也称为dunders。python 使用此命名约定在内部定义变量。避免使用此约定来防止 Python 更新可能出现的名称冲突。Dunder 变量的行为类似于单个前导下划线变量:它们在类中使用时不受名称修改的影响,但不会在通配符导入中导入。