跳至主要内容

Python 中下划线(_)的作用教程

在本教程中,您将学习下划线(_)在 Python 中的常见用法。
· 8分钟 阅读

使用 AI 探索

在线运行和编辑此教程中的代码

运行代码

许多Python 开发者并不了解 下划线(_)在Python中的用法。善用它能帮助您更高效地编写Python代码。

下划线(_)在Python中是一个很特殊的字符。

如果您是Python程序员,应该对下面的语法很熟悉:

  • for _ in range(100)

  • __init__(self)

  • _ = 2

在不同的场景下,它有一些特殊含义。我们一起来看一看。

您会发现下划线(_)大致有六种用法。了解这些之后,您也可以在合适的情境下灵活使用下划线(_)。

1. 在解释器中的用法

Python会自动把解释器中上一条表达式的值保存到名为“_”的特殊变量里。如果需要,您也可以把这个值再赋给其他变量。

它也可以像普通变量一样使用。看示例:

>>> 5 + 4
9
>>> _     # stores the result of the above expression
9
>>> _ + 6
15
>>> _
15
>>> a = _  # assigning the value of _ to another variable
>>> a
15

2. 忽略值

下划线(_)也常用于忽略某些值。如果在解包时不打算使用某些元素,只需把它们赋给下划线(_)。

“忽略”的意思是把值赋给特殊变量下划线(_),表示我们后续代码不会使用它。

示例如下:

## ignoring a value
a, _, b = (1, 2, 3) # a = 1, b = 3
print(a, b)

## ignoring multiple values
## *(variable) used to assign multiple value to a variable as list while unpacking
## it's called "Extended Unpacking", only available in Python 3.x
a, *_, b = (7, 6, 5, 4, 3, 2, 1)
print(a, b)
1 3
7 1

3. 在循环中的用法

在循环中您也可以把下划线(_)当作变量使用。看下面的例子:

## lopping ten times using _
for _ in range(5):
    print(_)

## iterating over a list using _
## you can use _ same as a variable
languages = ["Python", "JS", "PHP", "Java"]
for _ in languages:
    print(_)

_ = 5
while _ < 10:
    print(_, end = ' ') # default value of 'end' id '\n' in python. we're changing it to space
    _ += 1
0
1
2
3
4
Python
JS
PHP
Java
5 6 7 8 9

4. 数字分组

当数字很长时,您可以用下划线按需分组,以便更易读。

例如:million = 1_000_000

此外,也可以用下划线(_)分隔二进制、八进制或十六进制数字的部分。

例如:binary = 0b_0010,octa = 0o_64,hexa = 0x_23_ab

运行以上示例查看结果。

## different number systems
## you can also check whether they are correct or not by coverting them into integer using "int" method
million = 1_000_000
binary = 0b_0010
octa = 0o_64
hexa = 0x_23_ab

print(million)
print(binary)
print(octa)
print(hexa)
1000000
2
52
9131

5. 使用下划线(_)进行命名

下划线(_)可用于变量、函数、类等的命名:

  • 单前导下划线:_variable
  • 单结尾下划线:variable_
  • 双前导下划线:__variable
  • 双前后下划线:__variable__

5.1. _single_pre_underscore

_name

单前导下划线用于表示内部使用。多数情况下我们正是因此而不去使用或访问它。

请看示例:

class Test:

    def __init__(self):
        self.name = "datacamp"
        self._num = 7

obj = Test()
print(obj.name)
print(obj._num)
datacamp
7

单前导下划线并不会阻止您访问带有单前导下划线的变量。

但是,单前导下划线会影响从模块中导入的名称。

让我们在my_funtions文件中写下如下代码。

## filename:- my_functions.py

def func():
    return "datacamp"

def _private_func():
    return 7

现在,如果您从my_functions.py中导入所有方法和名称,Python不会导入以单前导下划线开头的名称。

>>> from my_functions import *
>>> func()
'datacamp'
>>> _private_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_private_func' is not defined

通过以常规方式导入模块可以避免上述错误。

>>> import my_functions
>>> my_functions.func()
'datacamp'
>>> my_functions._private_func()
7

单前导下划线仅用于表示内部使用。

5.2 single_postunderscore

name_

有时如果您想将Python 关键字用作变量、函数或类名,可以采用这种约定。

在您想用的名称末尾加一个下划线,即可避免与Python 关键字冲突。

来看示例:

>>> def function(class):
  File "<stdin>", line 1
    def function(class):
                 ^
SyntaxError: invalid syntax
>>> def function(class_):
...     pass
...
>>>

单结尾下划线用于当您的命名与Python 关键字冲突时,在变量名末尾添加下划线以避免冲突。

5.3. 双前导下划线

__name

双前导下划线用于名称改写(name mangling)。

双前导下划线会让Python解释器重写子类中的属性名,以避免命名冲突。

  • 名称改写:Python解释器会以一种方式改写变量名,使其在类被继承时更不容易发生冲突。

看一个示例:

class Sample():

    def __init__(self):
        self.a = 1
        self._b = 2
        self.__c = 3
obj1 = Sample()
dir(obj1)
['_Sample__c',
 '__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']

上述代码会返回该类对象的所有属性。我们来看看变量在属性列表中的表现。

self.a 直接原样出现。

self._b 也原样出现。正如前文所述,它只是用于内部约定。

  • 列表中有 self.__c 吗?

    • 如果仔细看属性列表,会发现一个名为 _Sample__c 的属性。这就是名称改写,用于避免子类中对该变量的覆盖发生冲突。

我们通过继承 Sample类再创建一个类,看看覆盖时会发生什么。

class SecondClass(Sample):

    def __init__(self):
        super().__init__()
        self.a = "overridden"
        self._b = "overridden"
        self.__c = "overridden"
obj2 = SecondClass()
print(obj2.a)
print(obj2._b)
print(obj2.__c)
overridden
overridden



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-2-4bf6884fbd34> in <module>()
      9 print(obj2.a)
     10 print(obj2._b)
---> 11 print(obj2.__c)


AttributeError: 'SecondClass' object has no attribute '__c'

这里再次体现了名称改写:它把 obj2.__c 改为 _SecondClass__c。现在用改写后的属性名来打印它。

print(obj2._SecondClass__c)
overridden

看,成功了。您也可以通过 _Sample__c 访问之前创建的变量。如下:

print(obj1._Sample__c)
3

在类的方法中也可以访问双前导下划线变量。看示例:

class SimpleClass:

    def __init__(self):
        self.__datacamp = "Excellent"

    def get_datacamp(self):
        return self.__datacamp

obj = SimpleClass()
print(obj.get_datacamp()) ## it prints the "Excellent" which is a __var
print(obj.__datacamp)     ## here, we get an error as mentioned before. It changes the name of the variable
Excellent



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-8006c0a9b061> in <module>()
      9 obj = SimpleClass()
     10 print(obj.get_datacamp()) ## it prints the "Excellent" which is a __var
---> 11 print(obj.__datacamp)     ## here, we get an error as mentioned before. It changes the name of the variable


AttributeError: 'SimpleClass' object has no attribute '__datacamp'

您也可以把双前导下划线用于方法名。示例如下:

class SimpleClass:

    def __datacamp(self):
        return "datacamp"

    def call_datacamp(self):
        return self.__datacamp()

obj = SimpleClass()
print(obj.call_datacamp()) ## same as above it returns the Dobule pre underscore method
print(obj.__datacamp())    ## we get an error here
datacamp



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-1-cd8ce2e83589> in <module>()
      9 obj = SimpleClass()
     10 print(obj.call_datacamp()) ## same as above it returns the Dobule pre underscore method
---> 11 print(obj.__datacamp())    ## we get an error here


AttributeError: 'SimpleClass' object has no attribute '__datacamp'

我们再用另一种方式看名称改写。先创建一个名为 _SimpleClass__name 的变量,然后尝试用双前导下划线的名字来访问它。

示例如下:

_SimpleClass__name = "datacamp"

class SimpleClass:

    def return_name(self):
        return __name

obj = SimpleClass()
print(obj.return_name()) ## it prints the __name variable
datacamp

理解这个概念了吗?如果还没有,建议再读一遍。

5.4. 双前后下划线

__name__

在Python中,您会看到很多以双下划线开头并结尾的名称。它们被称为魔术方法(magic methods)或dunder 方法。

class Sample():

    def __init__(self):
        self.__num__ = 7

obj = Sample()
obj.__num__
7

如果把这类名称用作您的变量名,可能会引发冲突。因此,最好避免使用它们。

结语

恭喜您!坚持看完了。这可能是Python里最乏味的概念之一,但在处理复杂代码时非常有用。

像我这样的大多数人第一次读并不一定能完全理解。所以别着急,如果第一次没看懂,就再读一遍。如果您对本文有任何疑问,欢迎在评论区提出。

如果您是Python初学者,建议先学习这门课程,然后再回过头来阅读本文,会更容易全面掌握。

参考资料:

主题