Python中单、双下划线的区别总结

本文解析Python中单下划线、双下划线及特殊方法的用途,探讨其对代码设计的影响,以及如何正确使用它们。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Python 的代码风格由 PEP 8 描述。这个文档描述了 Python 编程风格的方方面面。在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格。这样就易于阅读,易于在程序员之间交流。

我们大家在学习Python的时候,好像很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__这种。在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语言中的private、public的作用一样,但仔细深究,这不全是Python这样设计的初衷。

下面我们具体分析,话不多说了,来一起看看吧。

单下划线开头

我们经常看到方法或者属性前面加了单下划线,并认为它表示该方法或者属性是该类型(Python和Golang一样,不光类可以有方法,很多类型甚至基本类型也可以定义方法)的私有方法或属性。但其实在Python中不存在真正意义上的私有方法或者属性,前面加单下划线_只是表示你不应该去访问这个方法或者属性,因为它不是API的一部分。

举个例子:

?

1

2

3

4

5

6

7

8

9

10

11

Python

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属性是对外API的一部分,如果你想获取错误详情,应该访问errors属性,而不是(也不应该)访问_get_errors方法。

双下划线开头

之前很多人跟我说Python中双下划线开头表示私有,我在很多地方也见到这样的说法。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。

我们看个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

class A(object):

  

 def __method(self):

 print("I'm a method in class A")

 

 def method_x(self):

 print("I'm another method in class A\n")

 

 def method(self):

 self.__method()

 self.method_x()

 

class B(A):

  

 def __method(self):

 print("I'm a method in class B")

 

 def method_x(self):

 print("I'm another method in class B\n")

 

 

if __name__ == '__main__':

  

 print("situation 1:")

 a = A()

 a.method()

 

 b = B()

 b.method()

 

 print("situation 2:")

 # a.__method()

 a._A__method()

执行结果:

?

1

2

3

4

5

6

7

8

9

situation 1:

I'm a method in class A

I'm another method in class A

 

I'm a method in class A

I'm another method in class B

 

situation 2:

I'm a method in class A

这里有两个点需要注意:

A类中我们定义了__method()、method_x和method()三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()和method_x方法,但是从输出结果看,B对象调用method()方法时调用了其父类A的__method()方法和自己的method_x()方法。也就是说,__method()覆写没有生效,而method_x()覆写生效了。而这也正是Python设计双下划线开头的唯一目的。

这一点也可在Python官方说明中得到答案:https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables

前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀_类名,所以就像situation 2下面的代码,虽然我们不能用a直接访问__method(),但却可以加上前缀去访问,即_A__method()。

开头结尾双下划线

一般来说像__this__这种开头结尾都加双下划线的方法表示这是Python自己调用的,你不要调用。比如我们可以调用len()函数来求长度,其实它后台是调用了__len__()方法。一般我们应该使用len,而不是直接使用__len__():

?

1

2

3

4

5

6

7

a = [1, 2, 3]

print(len(a))

print(a.__len__()) # 和上面等效

 

num = 10

print(num + 10)

print(num.__add__(10)) # 和上面等效

我们一般称__len__()这种方法为magic methods,一些操作符后台调用的也是也是这些magic methods,比如+后台调用的是__add__,-调用的是__sub__,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()会在对象的初始化时调用,__new__()会在构建一个实例的时候调用等等。下面我们看两个例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

class CrazyNumber(object):

 def __init__(self, n):

 self.n = n

 def __add__(self, other):

 return self.n - other

 def __sub__(self, other):

 return self.n + other

 def __str__(self):

 return str(self.n)

 

num = CrazyNumber(10)

print(num) # output is: 10

print(num + 5) # output is: 5

print(num - 20) # output is: 30

在上面这个例子中,我们覆写了+和-操作符,将他们的功能交换了。再看个例子:

?

1

2

3

4

5

6

7

8

9

10

11

class Room(object):

 def __init__(self):

 self.people = []

 def add(self, person):

 self.people.append(person)

 def __len__(self):

 return len(self.people)

  

room = Room()

room.add("Igor")

print len(room) # output is: 1

这个例子中,因为我们实现了__len__(),所以Room对象也可以使用len函数了。

所有此类的方法都在这里有说明:documentation.

结论

  • 使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。
  • 使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。
  • 当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如len、add、+、-、==等)时,你可以定义该类方法。
  • 当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。

注:本文大部分内容参考自Difference between _ , and __xx in Python .

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

<think>首先,用户的问题是:“我想了解Python下划线的用途和含义 请问Python 下划线的作用和使用场景”。我需要基于提供的引用内容来回答。 从系统级指令,我必须: - 使用中文回答。 - 结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 在回答中引用的段落末尾自然地添加引用标识,如 [^1]。 - 生成相关问题。 提供的引用: - [^1]: 关于下划线(_foo)和下划线(__foo)的含义,提到下划线表示内部使用或私有。 - [^2]: 下划线的四种使用场景:分隔数字、忽略变量、在解释器中存储最后一个表达式、命名约定(/前置下划线个后置下划线前置和后置下划线)。 - [^3]: 下划线下划线在函数定义中的含义:下划线开头表示内部使用或私有,下划线开头触发名称改写。 - [^4]: 下划线变量在扩展库中的使用,如 C 模块的包装。 - [^5]: 下划线下划线区别下划线是约定,表示内部使用;下划线有名称改写。 关键点总结: - **下划线(_)**: - 前置下划线(如 `_foo`):表示内部使用或私有,但 Python 不强制,只是约定。它不会被 `import *` 导入。 - 后置下划线(如 `foo_`):用于避免与 Python 关键字冲突,如 `class_`。 - 在解释器中:`_` 存储最后一个表达式的结果。 - 在数字中:用于分隔数字,如 `1_000_000`。 - 忽略变量:在循环或解包中,用 `_` 表示忽略的变量,如 `for _ in range(5)`。 - **下划线(__)**: - 前置下划线(如 `__foo`):在类中触发名称改写(name mangling),变成 `_ClassName__foo`,用于防止子类覆盖。 - 前后下划线(如 `__init__`):特殊方法或属性,Python 保留。 用户的问题只关注“下划线”,所以我应该重点回答下划线的用途和含义,但可以简要对比下划线以澄清。 回答结构: 1. **介绍**:简要解释下划线Python 中的重要性。 2. **主要用途和场景**:分点列出下划线的不同使用场景。 - 前置下划线:表示内部使用。 - 后置下划线:避免关键字冲突。 - 在解释器中:存储最后一个表达式。 - 在数字中:分隔数字。 - 忽略变量。 3. **示例**:提供代码示例来阐明。 4. **与下划线区别**:简要说明以区分。 5. **总结**:强调下划线是约定而非强制。 6. **生成相关问题**:基于主题,生成相关的问题。 确保引用: - 在相关部分添加引用标识,如 [^1], [^2], 等。 生成相关问题: - 例如:1. 下划线Python 中的用途是什么?2. 如何正确使用下划线来命名变量?3. Python 中的私有成员是如何实现的? 最终输出格式: - 使用中文。 - 数学表达式用 $...$ 或 $$...$$ 如果出现(但这里可能不需要)。 - 代码块用 ```python ... ```。 - 结尾添加相关问题。</think>### Python 下划线的用途和含义 在 Python 中,下划线(`_`)是一个多功能符号,主要用于命名约定和特殊场景,以增强代码的可读性和维护性。它不是 Python 的强制语法规则,而是一种广泛遵循的编程约定(PEP 8 风格指南推荐)。下划线的含义和用途取决于其上下文位置(如前置、后置或独使用)。下面我将逐步解释其核心作用和使用场景,基于可靠来源和 Python 官方实践。 #### 1. **前置下划线(如 `_variable` 或 `_function()`)** - **含义**:表示该变量、方法或函数是“内部使用”或“私有”的,建议仅在模块或类内部访问,而不应从外部直接调用。这有助于区分公共接口和实现细节。 - **使用场景**: - **模块级私有**:在模块中定义时,`import *` 不会导入以下划线开头的名称(例如,`from module import *` 会忽略 `_private_var`)。这防止命名冲突并封装内部逻辑[^1][^5]。 - **类内部私有**:在类中,前置下划线表示该成员不应被外部直接访问,但 Python 并不强制私有化(即仍可访问)。例如: ```python class MyClass: def __init__(self): self._internal_data = 42 # 内部变量,约定不直接访问 def _helper_method(self): # 内部方法,用于辅助公共方法 return self._internal_data * 2 ``` - **C 扩展库包装**:在 Python 包装 C 编写的扩展模块时,常使用下划线前缀命名 C 模块(如 `_struct`),然后通过无下划线Python 模块(如 `struct`)暴露接口[^4]。 #### 2. **后置下划线(如 `variable_` 或 `class_()`)** - **含义**:主要用于避免与 Python 关键字或内置名称冲突,是一种命名解决策略。 - **使用场景**: - **避免关键字冲突**:当变量名与 Python 保留字(如 `class`、`def`)冲突时,添加后置下划线使其合法。例如: ```python class_ = "MyClass" # 避免与关键字 class 冲突 def calculate_sum(list_): # 避免与内置 list 冲突 return sum(list_) ``` - 这遵循 PEP 8 指南,确保代码清晰且无歧义[^2][^5]。 #### 3. **独使用下划线(`_`)** - **含义**:作为临时或忽略变量,不关心其具体值。 - **使用场景**: - **忽略不需要的变量**:在循环、解包或函数返回值中,用 `_` 表示忽略的项。例如: ```python for _ in range(5): # 忽略迭代次数,仅执行循环体 print("Hello") x, _, y = (1, 2, 3) # 忽略中间值 2 ``` - **在交互式解释器中存储结果**:在 Python REPL(如 IDLE 或 Jupyter)中,`_` 自动存储最后一个表达式的结果。例如: ```python >>> 3 + 4 7 >>> _ * 2 # 引用上一个结果 7 14 ``` 这提高了开发效率,尤其在调试时[^2][^3]。 #### 4. **在数字中作为分隔符(如 `1_000_000`)** - **含义**:提高大数字的可读性,类似于英文中的逗号分隔符。 - **使用场景**: - 在定义整数或浮点数时,使用 `_` 分隔千位、百万位等。Python 会忽略这些下划线,仅处理数值。例如: ```python population = 7_800_000_000 # 等价于 7800000000 ``` 这符合 PEP 515 标准,使代码更易读[^2]。 #### 与下划线区别 下划线(`_`)主要是一种约定,而下划线(`__`)在类中触发名称改写(name mangling),例如 `__method` 会被改写为 `_ClassName__method` 以防止子类覆盖[^3][^5]。下划线更注重可读性和约定,下划线涉及语法级行为。 #### 总结 Python下划线是一个灵活的约定工具: - **前置**:表示“内部使用”,封装实现细节。 - **后置**:避免命名冲突。 - **独使用**:忽略变量或在解释器中存储结果。 - **数字分隔**:增强可读性。 尽管下划线不强制私有化,但遵循这些约定能提升代码质量和团队协作效率[^1][^2][^5]。在实际开发中,建议结合 PEP 8 指南使用,例如在函数定义或变量命名时明确意图。 ### 相关问题 1. Python下划线(如 `__init__`)的作用是什么? 2. 如何正确使用下划线来避免 Python 关键字冲突? 3. Python 中的“私有成员”是如何实现的?下划线下划线有何区别? [^1]: Python变量和方法名称中下划线下划线的含义,包括内部使用约定。 [^2]: Python下划线的四种使用场景,如分隔数字、忽略变量和解释器存储。 [^3]: 下划线下划线在函数定义中的含义,包括名称改写规则。 [^4]: 下划线在C扩展库包装中的应用。 [^5]: 下划线下划线区别,强调约定而非强制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值