文章目录

  • 1. 单前导下划线,如 `_var`
  • 2. 单末尾下划线:如 `var_`
  • 3. 双前导下划线:如 `__var`
  • 4. 双前导和末尾下划线:如 `__var__`
  • 5. 单下划线:如 `_`
  • 总结


单下划线和双下划线在 Python 变量和方法名中都各有其含义。有一些含义仅仅是依照约定,被视作是对程序员的提示,而有一些含义是由 Python 解释器严格执行的。

本文主要介绍五种下划线模式和命名约定,以及它们如何影响 Python 程序的行为:

  • 单前导下划线,如 _var
  • 单末尾下划线,如 var_
  • 双前导下划线,如 __var
  • 双前导和末尾下划线,如 __var__
  • 单下划线,如 _

1. 单前导下划线,如 _var

以单个下划线开头的变量或方法,表示这个变量或方法是供内部使用的。这只是一个 Python 命名约定,通常不由 Python 解释器强制执行,仅仅作为一种对程序员的提示。

示例:

class Test:
    def __init__(self):
        self.a = 11
        self._b = 22

t = Test()
print(t.a)
print(t._b)

输出:

11
22

可以看到,变量 _b 中的单个下划线并没有阻止我们“进入”类并访问该变量的值。这是因为 Python 中的单个下划线前缀仅仅是一个约定。

但是,前导下划线会影响从模块中导入名称的方式。例如,创建一个 module.py 模块,有两个方法,如下:

def external_f():
    return 24

def _internal_f():
    return 8

在另一个模块 module1.py 使用通配符从 module.py 中导入所有名称,则 Python 不会导入带有前导下划线的名称(除非模块定义了覆盖此行为的 __all__ 列表):

from my_note.the_meaning_of_underscore.module import *

print(external_f())

print(_internal_f()) # 报错,找不到_internal_f()

顺便说一下,应该避免通配符导入,因为它们使名称空间中存在哪些名称不清楚。 为了清楚起见,坚持常规导入更好。常规导入不受前导单个下划线命名约定的影响,如下:

from my_note.the_meaning_of_underscore.module import external_f, _internal_f

print(external_f())

print(_internal_f())

输出:

24
8

2. 单末尾下划线:如 var_

单个末尾下划线(后缀)是一个约定,用来避免与 Python 关键字产生命名冲突。

有时候,一个变量最合适的名称已经被一个关键字占用。此时,你可以附加一个末尾下划线来解决命名冲突:

# 报错:SyntaxError: "invalid syntax"
def f(class):
    print("")

# 在末尾添加一个下划线
def f(class_):
    print("")

3. 双前导下划线:如 __var

双下划线前缀会导致 Python 解释器重写属性名称,以避免子类中的命名冲突。这也叫做名称修饰——解释器更改变量的名称,以便在类被扩展的时候不容易产生冲突

双前导下划线变量表明该变量是一个私有变量。Python 默认的成员函数和成员变量都是公开的,没有像 Java 语言的 publicprivate 等关键字修饰。但是可以在变量前面加上两个下划线,这样的话函数或变量就变成私有的。

先看个例子:

class Test:
   def __init__(self):
       self.a = 1
       self._b = 2
       self.__b = 3

使用 dir() 函数来查看这个类对象的属性:

print(dir(Test()))

输出:
['_Test__b', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', 
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
'__weakref__', '_b', 'a']

以上是这个类对象的属性列表。现在从中寻找我们的原始变量名称 a_b__b

  • self.a 变量在属性列表中仍然显示为 a
  • self._b 变量在属性列表中显示为 _b(前导下划线仅仅是一个约定,给程序员一个提示而已);
  • 而对于变量 self.__b,属性列表中没有 __b 名称。但是有一个 _Test__b 属性,这就是 Python 解释器所做的名称修饰(在变量前面加上一个下划线和类名),这是为了防止变量在子类中被重写

创建一个 Test 类的扩展类,并尝试重写构造函数中添加的现有属性:

class ExtendTest(Test):
    def __init__(self):
    	super().__init__()
        self.a = 11
        self._b = 22
        self.__b = 33

et = ExtendTest()
print(et.a)
print(et._b)
print(et.__b)

打印结果显示变量 a_b 的值已经被重写,而在打印 __b 的时候会报错:AttributeError: 'ExtendTest' object has no attribute '__b',显示找不到这个对象的 __b 属性。再使用 dir() 查看这个对象的属性列表:

['_ExtendTest__b', '_Test__b', '__class__', '__delattr__', '__dict__', '__dir__', 
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', 
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', '__weakref__', '_b', 'a']

我们可以看到 __b 变成 _ExtendTest__b 以防止意外修改:

print(et._ExtendTest__b) # 33

并且,父类的 _Test__b 属性依然存在:

print(et._Test__b) # 3

由此,我们可以看出,双前导下划线变量就是一个 private 变量。并且,名称修饰也适用于方法名,名称修饰会影响一个类的上下文中以两个下划线字符开头的所有名称。

4. 双前导和末尾下划线:如 __var__

对于有双前导和双末尾下划线的名称,这是 Python 内部定义的变量名,用于特殊用途。

例如,构造函数名声明为 __init__

class Test:
    def __init__(self):
       self.__number = 24

Python 内部还有 __name____main__ 等保留名称。最好避免自己定义这类变量,以避免与将来 Python 语言的变化产生冲突。

5. 单下划线:如 _

按照习惯,有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。

例如,在循环中,我们不需要访问正在运行的索引,我们就可以使用 _ 来表示它只是一个临时值:

list = [1, 2, 3]

for _ in list:
    print(_)

输出:

1
2
3

总结

  • 单前导下划线,如 _var:命名约定,供内部使用的。通常不由 Python 解释器强制执行,仅仅作为一种对程序员的提示。
  • 单末尾下划线,如 var_: 命名约定,用来避免与 Python 关键字产生命名冲突。
  • 双前导下划线,如 __var:私有变量。名称修饰,由 Python 解释器强制执行。
  • 双前导和末尾下划线,如 __var__:Python 内部定义的变量名,用于特殊用途。
  • 单下划线,如 _:表示某个变量是临时的或无关紧要的。