详解Python中下划线的5种含义


Posted in Python onJuly 15, 2021
目录
  • 1. 单引号下划线 _var
  • 2. 单尾划线 var_
  • 3. 双首下划线 __var
    • __baz出什么情况了?
  • 4. 双前导和双末尾下划线 _var_
  • 5.单下划线 _
  • Python下划线命名模式 - 小结

本文介绍了Python中单下划线和双下划线("dunder")的各种含义和命名约定,名称修饰(name mangling)的工作原理,以及它如何影响你自己的Python类。

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

如果你想知道“Python 变量和方法名称中单下划线和双下划线的含义是什么?”,我会尽我所能在这里为你解答。

在本文中,我将讨论以下五种下划线模式和命名约定,以及它们如何影响 Python 程序的行为:

  • 单前导下划线:_var
  • 单末尾下划线:var_
  • 双前导下划线:__var
  • 双前导和末尾下划线:_var_
  • 单下划线:_

在文章结尾处,你可以找到一个简短的“速查表”,总结了五种不同的下划线命名约定及其含义,以及一个简短的视频教程,可让你亲身体验它们的行为。

让我们马上开始!

 

1. 单引号下划线 _var

当涉及到变量和方法名称时,单个下划线前缀有一个约定俗成的含义。 它是对程序员的一个提示,意味着 Python 社区一致认为它应该是什么意思,但程序的行为不受影响。

下划线前缀的含义是告知其他程序员:以单个下划线开头的变量或方法仅供内部使用。 该约定在 PEP8 中有定义。

这不是 Python 强制规定的。 Python 不像 Java 那样在“私有”和“公共”变量之间有很强的区别。 这就像有人提出了一个小小的下划线警告标志,说:

“嘿,这不是真的要成为类的公共接口的一部分。不去管它就好。“

看看下面的例子:

class Test:
   def __init__(self):
       self.foo = 11
       self._bar = 23

如果你实例化此类,并尝试访问在__init__构造函数中定义的 foo 和 _bar 属性,会发生什么情况? 让我们来看看:

>>> t = Test()
>>> t.foo
11
>>> t._bar
23

你会看到 _bar 中的单个下划线并没有阻止我们“进入”类并访问该变量的值。
这是因为 Python 中的单个下划线前缀仅仅是一个约定,至少相对于变量和方法名而言。
但是,前导下划线的确会影响从模块中导入名称的方式。
假设你在一个名为 my_module 的模块中有以下代码:

# This is my_module.py:

def external_func():
   return 23

def _internal_func():
   return 42

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

>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
NameError: "name '_internal_func' is not defined"

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

>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42

我知道这一点可能有点令人困惑。 如果你遵循 PEP8 推荐,避免通配符导入,那么你真正需要记住的只有这个:

单个下划线是一个 Python 命名约定,表示这个名称是供内部使用的。 它通常不由 Python 解释器强制执行,仅仅作为一种对程序员的提示。

 

2. 单尾划线 var_

有时候,一个变量的最合适的名称已经被一个关键字所占用。 因此,像 class 或 def 这样的名称不能用作 Python 中的变量名称。 在这种情况下,你可以附加一个下划线来解决命名冲突:

>>> def make_object(name, class):
SyntaxError: "invalid syntax"

>>> def make_object(name, class_):
...    pass

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

 

3. 双首下划线 __var

到目前为止,我们所涉及的所有命名模式的含义,来自于已达成共识的约定。 而对于以双下划线开头的 Python 类的属性(包括变量和方法),情况就有点不同了。
双下划线前缀会导致 Python 解释器重写属性名称,以避免子类中的命名冲突。
这也叫做名称修饰(name mangling),解释器更改变量的名称,以便在类被扩展的时候不容易产生冲突。
我知道这听起来很抽象。 因此,我组合了一个小小的代码示例来予以说明:

class Test:
 def __init__(self):
  self.foo = 11
  self._bar = 23
  self.__baz = 23

让我们用内置的 dir() 函数来看看这个对象的属性:

>>> t = Test()
>>> dir(t)
['_Test__baz', '__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__', '_bar', 'foo']

以上是这个对象属性的列表。 让我们来看看这个列表,并寻找我们的原始变量名称 foo,_bar和 __baz , 我保证你会注意到一些有趣的变化。

  • self.foo 变量在属性列表中显示为未修改为 foo。
  • self._bar 的行为方式相同 - 它以 _bar 的形式显示在类上。 就像我之前说过的,在这种情况下,前导下划线仅仅是一个约定。 给程序员一个提示而已。
  • 然而,对于 self.__baz 而言,情况看起来有点不同。 当你在该列表中搜索 __baz 时,你会看不到有这个名字的变量。

 

__baz出什么情况了?

如果你仔细观察,你会看到此对象上有一个名为 _Test__baz 的属性。 这就是 Python 解释器所做的名称修饰。 它这样做是为了防止变量在子类中被重写。

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

class ExtendedTest(Test):
 def __init__(self):
  super().__init__()
  self.foo = 'overridden'
  self._bar = 'overridden'
  self.__baz = 'overridden'

现在,你认为 foo,_bar和 __baz 的值会出现在这个 ExtendedTest 类的实例上吗? 我们来看一看:

>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"

等一下,当我们尝试查看 t2 .__ baz 的值时,为什么我们会得到 AttributeError? 名称修饰被再次触发了! 事实证明,这个对象甚至没有 __baz 属性:

['_ExtendedTest__baz', '_Test__baz', '__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__', '_bar', 'foo']

正如你可以看到 __baz变成 _ExtendedTest__baz 以防止意外修改:

>>> t2._ExtendedTest__baz
'overridden'

但原来的 _Test__baz 还在:

>>> t2._Test__baz
42

双下划线名称修饰对程序员是完全透明的。 下面的例子证实了这一点:

class ManglingTest:
   def __init__(self):
       self.__mangled = 'hello'

   def get_mangled(self):
       return self.__mangled

>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError: "'ManglingTest' object has no attribute '__mangled'"

名称修饰是否也适用于方法名称? 是的,也适用。名称修饰会影响在一个类的上下文中,以两个下划线字符("dunders")开头的所有名称:

class MangledMethod:
   def __method(self):
       return 42

   def call_it(self):
       return self.__method()

>>> MangledMethod().__method()
AttributeError: "'MangledMethod' object has no attribute '__method'"
>>> MangledMethod().call_it()
42

这是另一个也许令人惊讶的运用名称修饰的例子:

_MangledGlobal__mangled = 23

class MangledGlobal:
   def test(self):
       return __mangled

>>> MangledGlobal().test()
23

在这个例子中,我声明了一个名为_MangledGlobal__mangled 的全局变量。然后我在名为 MangledGlobal 的类的上下文中访问变量。由于名称修饰,我能够在类的 test() 方法内,以 __mangled 来引用_MangledGlobal__mangled全局变量。
Python 解释器自动将名称__mangled 扩展为 _MangledGlobal__mangled,因为它以两个下划线字符开头。这表明名称修饰不是专门与类属性关联的。它适用于在类上下文中使用的两个下划线字符开头的任何名称。

有很多要吸收的内容吧。

老实说,这些例子和解释不是从我脑子里蹦出来的。我作了一些研究和加工才弄出来。我一直使用 Python,有很多年了,但是像这样的规则和特殊情况并不总是浮现在脑海里。

有时候程序员最重要的技能是“模式识别”,而且知道在哪里查阅信息。如果您在这一点上感到有点不知所措,请不要担心。慢慢来,试试这篇文章中的一些例子。

让这些概念完全沉浸下来,以便你能够理解名称修饰的总体思路,以及我向您展示的一些其他的行为。如果有一天你和它们不期而遇,你会知道在文档中按什么来查。

 

4. 双前导和双末尾下划线 _var_

也许令人惊讶的是,如果一个名字同时以双下划线开始和结束,则不会应用名称修饰。 由双下划线前缀和后缀包围的变量不会被Python解释器修改:

class PrefixPostfixTest:
   def __init__(self):
       self.__bam__ = 42

>>> PrefixPostfixTest().__bam__
42

但是,Python保留了有双前导和双末尾下划线的名称,用于特殊用途。 这样的例子有,_init__对象构造函数,或_call --- 它使得一个对象可以被调用。

这些dunder方法通常被称为神奇方法 - 但Python社区中的许多人(包括我自己)都不喜欢这种方法。
最好避免在自己的程序中使用以双下划线(“dunders”)开头和结尾的名称,以避免与将来Python语言的变化产生冲突。

 

5.单下划线 _

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

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

>>> for _ in range(32):
...    print('Hello, World.')

你也可以在拆分(unpacking)表达式中将单个下划线用作“不关心的”变量,以忽略特定的值。 同样,这个含义只是“依照约定”,并不会在Python解释器中触发特殊的行为。 单个下划线仅仅是一个有效的变量名称,会有这个用途而已。

在下面的代码示例中,我将汽车元组拆分为单独的变量,但我只对颜色和里程值感兴趣。 但是,为了使拆分表达式成功运行,我需要将包含在元组中的所有值分配给变量。 在这种情况下,“_”作为占位符变量可以派上用场:

>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car

>>> color
'red'
>>> mileage
3812.4
>>> _
12

除了用作临时变量之外,“_”是大多数Python REPL中的一个特殊变量,它表示由解释器评估的最近一个表达式的结果。
这样就很方便了,比如你可以在一个解释器会话中访问先前计算的结果,或者,你是在动态构建多个对象并与它们交互,无需事先给这些对象分配名字:

>>> 20 + 3
23
>>> _
23
>>> print(_)
23

>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]

 

Python下划线命名模式 - 小结

以下是一个简短的小结,即“速查表”,罗列了我在本文中谈到的五种Python下划线模式的含义:

详解Python中下划线的5种含义

到此这篇关于详解Python中下划线的5种含义的文章就介绍到这了,更多相关Python 下划线内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
零基础写python爬虫之使用urllib2组件抓取网页内容
Nov 04 Python
Python计算回文数的方法
Mar 11 Python
Python的SQLAlchemy框架使用入门
Apr 29 Python
剖析Python的Twisted框架的核心特性
May 25 Python
Python实战小程序利用matplotlib模块画图代码分享
Dec 09 Python
python字符串string的内置方法实例详解
May 14 Python
Python多线程原理与用法详解
Aug 20 Python
使用Python实现从各个子文件夹中复制指定文件的方法
Oct 25 Python
如何使用Python实现自动化水军评论
Jun 26 Python
django框架F&Q 聚合与分组操作示例
Dec 12 Python
python绘制彩虹图
Dec 16 Python
tensorflow 2.0模式下训练的模型转成 tf1.x 版本的pb模型实例
Jun 22 Python
Python操作CSV格式文件的方法大全
openstack中的rpc远程调用的方法
Python实现查询剪贴板自动匹配信息的思路详解
如何利用Python实现一个论文降重工具
python实现MD5进行文件去重的示例代码
深入浅析python3 依赖倒置原则(示例代码)
Jul 09 #Python
Python中Selenium对Cookie的操作方法
You might like
PHP 开发环境配置(测试开发环境)
2010/04/28 PHP
PHP中数组定义的几种方法
2013/09/01 PHP
JavaScript入门学习书籍推荐
2008/06/12 Javascript
分享十五个最佳jQuery 幻灯插件和教程
2010/03/27 Javascript
裁剪字符串trim()自定义改进版
2013/04/10 Javascript
将字符串转换成gb2312或者utf-8编码的参数(js版)
2013/04/10 Javascript
js实现右下角可关闭最小化div(可用于展示推荐内容)
2013/06/24 Javascript
js中substr,substring,indexOf,lastIndexOf的用法小结
2013/12/27 Javascript
jQuery的animate函数学习记录
2014/08/08 Javascript
javascript实现控制文字大中小显示
2015/04/28 Javascript
一个简易时钟效果js实现代码
2020/03/25 Javascript
微信小程序调用摄像头隐藏式拍照功能
2018/08/22 Javascript
JS散列表碰撞处理、开链法、HashTable散列示例
2019/02/08 Javascript
利用Webpack实现小程序多项目管理的方法
2019/02/25 Javascript
Electron 调用命令行(cmd)
2019/09/23 Javascript
基于vue实现图片验证码倒计时60s功能
2019/12/10 Javascript
VUE UPLOAD 通过ACTION返回上传结果操作
2020/09/07 Javascript
Djang中静态文件配置方法
2015/07/30 Python
Python检查和同步本地时间(北京时间)的实现方法
2018/12/03 Python
pyqt5中QThread在使用时出现重复emit的实例
2019/06/21 Python
django中SMTP发送邮件配置详解
2019/07/19 Python
如何使用python操作vmware
2019/07/27 Python
Python脚本操作Excel实现批量替换功能
2019/11/20 Python
使用python实现时间序列白噪声检验方式
2020/06/03 Python
使用已经得到的keras模型识别自己手写的数字方式
2020/06/29 Python
canvas如何实现多张图片编辑的图片编辑器
2020/03/10 HTML / CSS
英国人最爱的饰品网站:Accessorize
2016/08/22 全球购物
syb养殖创业计划书
2014/01/09 职场文书
幼儿园教师考核制度
2014/02/01 职场文书
车辆年审委托书范本
2014/09/18 职场文书
第二批党的群众路线教育实践活动总结报告
2014/10/30 职场文书
2014年接待工作总结
2014/11/26 职场文书
创业计划书之物流运送
2019/09/17 职场文书
Redis如何使用乐观锁(CAS)保证数据一致性
2022/03/25 Redis
解决WINDOWS电脑开机后桌面没有任何图标
2022/04/09 数码科技
PostgreSQL聚合函数介绍以及分组和排序
2022/04/12 PostgreSQL