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

问题描述 投票:1120回答:14

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

python naming-conventions
14个回答
1033
投票

单下划线

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

引用PEP-8

_single_leading_underscore:弱“内部使用”指标。例如。 from M import *不会导入名称以下划线开头的对象。

双下划线(名称管理)

来自the Python docs

形式为__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!'}

3
投票

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

mangled

如果您随后在python REPL中创建子实例,您将看到以下内容

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"

这对某些人来说可能是显而易见的,但它在一个复杂得多的环境中让我措手不及


3
投票

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

参考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'


3
投票

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

含义:

some_variable - ►公众任何人都可以看到这一点。

_some_variable - ►这是公开的,任何人都可以看到这个但是这是一个表示私有的约定......警告没有执行Python的强制执行。

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

老实说,https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references

“在Python中不存在除对象内部之外无法访问的”私有“实例变量”

这个例子:

According to Python documentation

2
投票

既然有这么多人指的是雷蒙德的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) ,我只会通过写下他说的话来简化一下:

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

talk

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

假设您没有在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 中保留perimeter的本地参考。现在,派生类Circle会覆盖Tire的实现,而不会触及perimeter。当你调用area时,理论上它应该仍然使用Tire(5).area()进行计算,但实际上它使用的是Circle.perimeter,这不是预期的行为。这就是为什么我们需要Circle中的本地参考。

但为什么Tire.perimeter而不是__perimeter?因为_perimeter仍然为派生类提供覆盖的机会:

_perimeter

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

如果你的类不会被继承,或者方法重写不会破坏任何东西,那么你根本就不需要class Tire(Circle): def perimeter(self): return Circle.perimeter(self) * 1.25 _perimeter = perimeter


0
投票

获得_和__的事实非常简单;其他答案很好地表达了他们。用法很难确定。

这就是我的看法:

__double_leading_underscore

应该用于表示函数不是公共用途,例如API。这和导入限制使它在c#中的行为与_ 非常相似。

internal

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

==>

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

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

问题是类的作者可能合法地认为“此属性/方法名称应该是私有的,只能从此类定义中访问”并使用__private约定。但是稍后,该类的用户可能会创建一个合法需要访问该名称的子类。因此,要么必须修改超类(可能很难或不可能),要么子类代码必须使用手动损坏的名称(最好是丑陋且易碎)。

但是在我看来,问题是如果没有IDE在你覆盖方法时警告你,如果你从一个基类中意外地覆盖了一个方法,那么找到错误可能需要一段时间。


288
投票

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

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

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


274
投票

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

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

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

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

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


190
投票

._variable是半私人的,仅仅是为了传统

.__variable经常被错误地认为是超级私人的,而它的实际意义仅仅是为了防止意外访问[1]

qazxsw roi通常保留用于内置方法或变量

如果你非常想要,你仍然可以访问.__variable__变量。双重下划线只是将变量命名或重命名为类似.__mangled的变量

例:

instance._className__mangled

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

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

>>> t = Test()
>>> t._b
'b'

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

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

通过访问>>> t._Test__a 'a' 而不仅仅是双下划线名称,您可以访问隐藏值


103
投票

开头的单下划线:

Python没有真正的私有方法。相反,在方法或属性名称的开头有一个下划线意味着您不应该访问此方法,因为它不是API的一部分。

instance._className__variable

(此代码段取自django源代码:django / forms / forms.py)。在此代码中,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) 是一个公共属性,但此属性调用的方法_get_errors是“私有”,因此您不应该访问它。

一开始有两个下划线:

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

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!

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

$ python test.py
I'm test method in class A
I'm test method in class A

输出将是....

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

b = B()
b.test()

正如我们所看到的,A.test()没有像我们预期的那样调用B .__ test()方法。但事实上,这是__的正确行为。名为__test()的两个方法会自动重命名(损坏)为_A__test()和_B__test(),因此它们不会意外覆盖。当您创建以__开头的方法时,这意味着您不希望任何人能够覆盖它,并且您只打算从其自己的类中访问它。

开头和结尾两个下划线:

当我们看到像$ python test.py I'm test method in class A 这样的方法时,请不要调用它。这是python打算调用的方法,而不是你。让我们来看看:

__this__

总有一个操作符或本机函数调用这些魔术方法。有时它只是在特定情况下的钩子python调用。例如,在调用>>> name = "test string" >>> name.__len__() 11 >>> len(name) 11 >>> number = 10 >>> number.__add__(40) 50 >>> number + 50 60 构建实例后创建对象时调用__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 guide


16
投票

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

this PDF

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

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

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


7
投票

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

from sphinx.locale import l_, _

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


5
投票

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

双引导和尾随下划线用于内置方法,例如class PrivateVarC(object): def get_x(self): pass def set_x(self, val): pass rwvar = property(get_p, set_p) ronly = property(get_p) __init__等。

不带尾随对应的双引导下划线也是惯例,但是,类方法将由解释器为__bool__。对于变量或基本函数名称,不存在差异。


3
投票

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

但是__double_underscore名称在模块中没有名称限制,例如。如果从模块导入所有内容(来自模块导入*),则不会导入以一个(或多个)下划线开头的名称,也不会在帮助(模块)中显示名称。

© www.soinside.com 2019 - 2024. All rights reserved.