Python 双下划线和单下划线的意义总结
原文: https://towardsdatascience.com/whats-the-meaning-of-single-and-double-underscores-in-python-3d27d57d6bd1
在 Python 中命名变量和函数时,你有没有好奇过单下划线和双下划线的不同含义?
我最近研究了这个主题以复习一些基础知识,并决定将其写下来,因为我确实知道有一天我会再次需要它。
这篇文章详细介绍了下划线的不同使用方式。它将涵盖:
- 单前导下划线:_foo
- 单个尾随下划线:foo_
- 单下划线:_
- 双前导和尾随下划线(用于定义dunder 方法):bar
- 双前导下划线:__bar
了解每种语法的含义以及何时应该使用它,将帮助您在可读性和效率方面提高代码质量。
这也将帮助您遵守标准的命名约定。
事不宜迟,让我们来看看一些实际的例子🔍
# 1 单前导下划线:_foo
变量、函数或方法名称前的单个前导下划线表示这些对象在内部使用。
这更像是对程序员的语法提示,并不是由 Python 解释器强制执行的,这意味着这些对象仍然可以从另一个脚本以一种方式访问另一种方式。
但是,在某些情况下无法访问具有前导下划线的变量。
module.py
考虑以下定义两个变量的文件。
# module.py
public_variable = 2
_private_variable = 1/2
2
3
4
如果您从解释器或其他地方使用通配符导入(尽管强烈建议不要这样做),您会注意到具有前导下划线的变量在命名空间中不可用。
>>> from module import *
>>> public_variable
2
>>> _private_variable
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-69-28da499e5a9b> in <module>
----> 1 _private_variable
NameError: name '_private_variable' is not defined
2
3
4
5
6
7
8
9
10
11
12
但是,正如我们前面所说,_private_variable
如果导入模块并直接调用变量名,仍然可以访问。
>>> import module
>>> module.public_variable
2
>>>module._private_variable
0.5
2
3
4
5
6
7
单前导下划线通常在类中用于定义“内部”属性。我在内部周围加上了双引号,因为在 Python 中没有内部属性这样的东西。前导下划线仍然是一个符号,应该这样对待。
如果你有一些不打算从类外部访问的属性,仅仅是因为它们仅在内部用于中间计算,你可以考虑为它们添加一个前导下划线——这将作为一个提示。
让我们看一个例子:考虑一个 Employee 类,它定义了一个_seniority属性,除其他外,该属性是计算薪酬包所需的。
import datetime
class Employee:
def __init__(self, first_name, last_name, start_date):
self.first_name = first_name
self.last_name = last_name
self.start_date = start_date
self._seniority = self._get_seniority(start_date)
def _get_seniority(self):
today_date = datetime.datetime.today().date()
seniority = (today_date - start_date).days
return seniority
def compute_compensation_package(self):
# use self._seniority here.
first_name = "John"
last_name = "Doe"
start_date = datetime.date(2021, 12, 1)
employee = Employee(first_name, last_name, start_date)
>>> employee._seniority
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如您所见,_seniority可以从类的外部访问该属性。
# 2 单尾随下划线:foo_
在某些情况下,您想要使用的变量名实际上是 Python 中的保留关键字,例如class, def, type,object等。
为避免这种冲突,您可以添加尾随下划线作为命名约定。
class_ = "A"
# 3 单下划线:_
在某些情况下,您会看到 python 开发人员使用单下划线。主要有下边几种:
# 定义临时或未使用的变量。
示例 #1
如果您不使用 for 循环的运行索引,您可以轻松地将其替换为单个下划线。
for _ in range(100):
do_some_work()
2
示例 #2
如果你的函数返回一个包含五个元素的元组,但你只需要使用其中的两个(例如第一个和第四个),你可以使用下划线来命名剩下的三个。
cpu, _, _, memory, _ = extract_laptop_specs(laptop)
# python 交互式解释器中最后一次评估的结果存储在“_”中
In [1]: a = 2
In [2]: a
Out[2]: 2
In [3]: _
Out[3]: 2
2
3
4
5
6
7
# 用作数字分组的视觉分隔符
根据PEP 515 (opens new window),现在可以将下划线添加到数字文字中以提高长数字的可读性。
下面是一个示例,您可以将十进制数按千位进行分组。
In [2]: 1_000
Out[2]: 1000
In [3]: 1_000_000
Out[3]: 1000000
In [4]: 1_000_000_000
Out[4]: 1000000000
2
3
4
5
6
7
8
# 4 双前导和尾随下划线:foo
双前导和尾部下划线用于定义特殊的通用类方法,称为dunder 方法( D ouble Under score methods的缩写)。
Dunder 方法是您仍然可以覆盖的保留方法。它们具有特殊的行为并且被称为不同的。例如:
__init__
用作类的构造函数__call__
用于使对象可调用__str__
用于定义当我们将对象传递给函数时屏幕上打印的内容print。
如您所见,Python 引入了这种命名约定来区分模块的核心方法和用户定义的方法。
如果您想了解更多有关 dunder 方法的信息,可以查看此链接 (opens new window)。
# 5 双前导下划线:__bar
双前导下划线通常用于名称修饰。
名称重整是解释器更改属性名称以避免子类中的命名冲突的过程。
让我们看下面的类来说明:
class Car:
def __init__(self):
self.color = "red"
self._speed = 70
self.__brand = "bmw"
car = Car()
2
3
4
5
6
7
car现在让我们使用内置方法检查对象的属性dir。
>>> print(dir(car))
['_Car__brand', '__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__', '_speed', 'color']
2
3
4
5
6
7
我们注意到color和_speed可用但不可用__brand。
但是,有一个_Car__brand
属性代替。这就是名称重整:解释器在属性前加上一个“_”加上类名。这样做是为了避免属性的值__brand在子类中被覆盖。
让我们创建一个子类并尝试覆盖以前的属性:
class ExtendedCar(Car):
def __init__(self):
super(ExtendedCar, self).__init__()
self.color = "green"
self._speed = 80
self.__brand = "audi"
extended_car = ExtendedCar()
2
3
4
5
6
7
8
现在让我们检查对象的属性extended_car:
>>> print(dir(extended_car))
['_Car__brand', '_ExtendedCar__brand', '__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__', '_speed', 'color']
2
3
4
5
6
7
8
如我们所见,添加了一个新属性:_ExtendedCar_brand
而_Car_brand
来自父类的 仍然存在。
如果我们尝试访问这两个属性:
>>> extended_car._Car__brand
'bmw'
>>> extended_car._ExtendedCar__brand
'audi'
2
3
4
5
我们注意到属性的值__brand
(来自父类)没有被覆盖,即使ExtendedCar类的定义会建议这样做。
# 资源
我从来不知道单下划线和双下划线在许多不同的上下文中会有同样多的含义。
了解它们很有趣,并且一如既往,这是我的资源列表,供您深入研究该主题。
- https://towardsdatascience.com/5-different-meanings-of-underscore-in-python-3fafa6cd0379
- https://www.python.org/dev/peps/pep-0515/
- https://www.python.org/dev/peps/pep-0008/
- 一篇关于 Dunders 的法语博文:https 😕/he-arc.github.io/livre-python/dunders/index.html
- https://www.section.io/engineering-education/dunder-methods-python/