1. 单前导下划线 _xxx
Python代码遵循一个规范:以单下划线开头的变量或方法应被视为非公开的API,用于指定该名称为“私有”,只供内部使用,外部调用者不应该去访问以单下划线开头的变量或方法。
虽然以from xxx import *
导入变量或方法时,除模块或包中 __all__
列表包含的 以_
开头的名称外,其余以_
开头的名称都不会被导入,这一定程度上体现了单前导下划线 _xxx 的“私有化”,但是PEP 8编码规范不推荐使用通配符 *
导入,即不推荐from xxx import *
的导入方式。离开了from xxx import *
的这种导入方式,“单前导下划线 _xxx 只供内部使用”只是一个命名约定成俗的规定,我们依然可以直接访问单前导下划线 _xxx 的变量或方法。观察下例:
class Test:
def __init__(self, ingredients, radius):
self.foo = 11
self._bar = 23
if __name__ == '__main__':
t = Test()
print(t.foo)
print(t._bar)
运行结果为:
11
23
2. 双前导下划线 __xxx 或 __xxx_
Python有一个非常简单的机制完成伪私有化功能,这个机制名叫名称转写(name mangling):以双下划线开头,并以最多一个下划线结尾的标识符, 例如__x,会被转写为_classname__x,其中classname为当前类名。观察下例:
class A:
def __mtehod_name(self):
pass
class B(A):
def __mtehod_name(self):
pass
if __name__ == '__main__':
print(dir(B()))
运行结果如下:
['_A__mtehod_name', '_B__mtehod_name', '__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__', '_internal_ues']
这样,不但可以一定程度上解决“单前导下划线 _xxx 的变量或方法可以被直接访问”的问题,不过依然可以间接通过转写名称来访问;而且还可以有效避免父子类同名方法的命名冲突,如上述同名方法为__mtehod_name
,名称转写后分别对应_A__mtehod_name
和_B__mtehod_name
。
上例无法体现名称转写如何有效避免父子类同名方法的命名冲突,改写上例,如若将双前导下划线去掉改为普通函数,父类A增加初始化代码并调用此普通函数,代码如下:
class A:
def __init__(self):
self.mtehod_name()
def mtehod_name(self):
print("调用了A类的mtehod_name方法")
class B(A):
def mtehod_name(self):
print("调用了B类的mtehod_name方法")
if __name__ == '__main__':
b = B()
运行结果为:调用了B类的mtehod_name方法
但是观察代码可知,创建B类的对象时,由于子类B无初始化方法,需要调用了父类A的__init__()方法进行初始化,而父类的__init__()方法中其实应该调用的是父类的方法mtehod_name(),而此时的运行结果显示调用了子类B类的同名方法mtehod_name(),这显然是不合理的!加回双前导下划线,观察运行结果:
class A:
def __init__(self):
self.__mtehod_name()
def __mtehod_name(self):
print("调用了A类的__mtehod_name方法")
class B(A):
def __mtehod_name(self):
print("调用了B类的__mtehod_name方法")
if __name__ == '__main__':
b = B()
运行结果:调用了A类的__mtehod_name方法
体现出双前导下划线__xxx有效避免父子类同名方法的命名冲突!
3. 单末尾下划线 xxx_
按照PEP 8编程规范:单末尾下划线 xxx_ 也是一个约定,用来避免与python关键字产生命名冲突。例如,如果想用class来用作变量名称,而class又是pytohn 关键字,此时可定义为class_ 。
4. 双前导和双末尾下划线 __var__
日常开发中,最好避免在自己的程序中使用以双下划线(“dunders”)开头和结尾的名称,因为它是Python语言定义的一种特殊方法(魔法方法),如熟知的__init__ 、_dict_ 、__getitem__等等。如果非要使用这种写法去声明,若声明的变量不是内置的魔法方法,Python会将它当做普通的变量来操作;若和内置方法重名,即重写内置方法,可能会因为功能冲突而引发报错
5. 单下划线 _
①在交互式解释器会话中,代表上一条执行的语句的结果
>>> _
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_' is not defined
>>> 42
42
>>> _
42
这种用法首先被标准CPython解释器采用,然后其他类型的解释器也先后采用。
②在python代码中,作为临时性的名称
应用场景:某些不关心且不会在后面用到的数据,可以采取分配临时名称存着。
例如,我们可能对循环计数中的实际值不感兴趣,只是想要循环遍历相应次数,此时就可以使用“_”。
例一
n = 42
for _ in range(n):
print('-' * 30)
例二
>>> car = ('red', 12, 3812.4)
>>> color, _, mileage = car
>>> color
'red'
>>> mileage
3812.4
>>> _
12
③字符串之间翻译查找的函数名称
也许我们曾看到”_“会被作为一个函数来使用。这种情况下,它通常用于实现国际化和本地化字符串之间翻译查找的函数名称,这似乎源自并遵循相应的C约定。例如,在Django文档“转换”章节中,你将能看到如下代码:
from django.utils.translation import ugettext as _
from django.http import HttpResponse
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)