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

时间:2009-08-19 17:11:57

标签: python naming-conventions

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

17 个答案:

答案 0 :(得分:1014)

单下划线

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

引用PEP-8

  

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

双下划线(名称管理)

来自the Python docs

  

__spam形式的任何标识符(至少两个前导下划线,最多一个尾随下划线)在文本上替换为_classname__spam,其中classname是具有前导下划线的当前类名(s)剥离。这种修改是在不考虑标识符的句法位置的情况下完成的,因此它可以用于定义类私有实例和类变量,方法,存储在全局变量中的变量,甚至存储在实例中的变量。在其他类的实例上对此类私有。

来自同一页面的警告:

  

名称修改旨在为类提供一种简单的方法来定义“私有”实例变量和方法,而不必担心派生类定义的实例变量,或者通过类外部的代码来修改实例变量。请注意,修剪规则主要是为了避免事故;确定的灵魂仍然可以访问或修改被视为私有的变量。

实施例

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

答案 1 :(得分:284)

迄今为止的优秀答案,但缺少一些花絮。单个前导下划线并不完全只是约定:如果使用from foobar import *,并且模块foobar未定义__all__列表,则从模块包括具有前导下划线的模块。让我们说它主要是 一个约定,因为这个案例是一个相当模糊的角落; - )。

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

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

答案 2 :(得分:269)

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

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

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

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

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

答案 3 :(得分:189)

._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'
找不到<。> .__ a,因为由于名称错误而不再存在

>>> t._Test__a
'a'

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

答案 4 :(得分:98)

开头的单下划线:

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 guide。有关更多魔术方法,请参阅this PDF

答案 5 :(得分:15)

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

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

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

from sphinx.locale import l_, _

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

答案 6 :(得分: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) 

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

答案 7 :(得分:5)

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

双引导和尾随下划线用于内置方法,例如__init____bool__等。

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

答案 8 :(得分:5)

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

    # 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 次“I must not talk in class”,并且永远不需要访问 _ 变量。

    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 变量的行为类似于单个前导下划线变量:它们在类中使用时不受名称修改的影响,但不会在通配符导入中导入。

答案 9 :(得分:3)

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

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'

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

答案 10 :(得分:3)

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

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

答案 11 :(得分:3)

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

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

答案 12 :(得分:1)

由于有这么多人指的是雷蒙德的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进行计算,但实际上,它使用的是Tire.perimeter,这不是预期的行为。这就是为什么我们在Circle中需要本地引用。

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

class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

    _perimeter = perimeter

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

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

答案 13 :(得分:1)

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

含义:

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

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

__ some_varaible--►Python将变量名替换为_classname__some_varaible(又名名称改写),从而降低/隐藏了它的可见性,更像是私有变量。

在这里According to Python documentation

  

“” Private“实例变量,除非从   对象内部不存在于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)

答案 14 :(得分:1)

根据https://dbader.org/blog/meaning-of-underscores-in-python

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

答案 15 :(得分:0)

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

这就是我的看法:

_

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

__

应该用于避免继承层hirarchy中的名称冲突并避免延迟绑定。很像c#中的私密。

==&GT;

如果您想表明某些内容不适合公众使用,但它应该像protected那样使用_。 如果您想表明某些内容不是供公众使用,但它应该像private那样使用__

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

  

问题在于,一个班级的作者可以合理地思考这个问题   属性/方法名称应该是私有的,只能从内部访问   这个班级定义&#34;并使用__private约定。但是后来,   该类的用户可以创建合法需要的子类   访问该名称。所以要么必须修改超类   (可能很难或不可能),或子类代码必须   使用手动损坏的名称(最好是丑陋和脆弱)。

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

答案 16 :(得分: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 错误时,我偶然发现了这一点。