Python中经常出现各种单下划线,双下划线,而且有的在前有的在后,有的是约定俗成的用法,有的则会强制对外隐藏。这一篇我们就一起来把各种下划线的用法说清楚。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
五种下划线的用法
Python中下划线有如下五种用法:
- 前置单下划线,例如
_var
- 后置单下划线,例如
var_
- 前置双下划线,例如
__var
- 前后双下划线,例如
__var__
- 单下划线,也就是单独的
_
下面就对这五种用法分别用例子来进行说明
操作环境
下面的演示环境为
- Python 3.7.1
- IPython 7.2.0
前置单下划线,例如_var
这是一种约定俗成的写法,用来告诉程序员像这样定义的属性或者方法建议只被内部属用。这是PEP8中明确定义的一种写法。
Python并不像Java那样有私有和公共变量的概念,所以并不会强制某个属性或者方法只能被内部使用。
看下示例。
在文件test_underscore.py
下定义如下类
class Test:
def __init__(self):
self.name = 'xiaofu'
self._englishname = 'victor'
def say(self):
print(self.name)
def _shout(self):
print(self._englishname)
然后实例一个对象出来,尝试访问两个属性和两个方法,发现都可以被正常访问
In [1]: import test_underscore
In [2]: xiaofu = test_underscore.Test()
In [3]: xiaofu.name
Out[3]: 'xiaofu'
In [4]: xiaofu._englishname
Out[4]: 'victor'
In [5]: xiaofu.say()
xiaofu
In [6]: xiaofu._shout()
victor
所以总结起来,前置单下划线只是一种对程序员的建议,该属性或者方法只被内部使用。Python本身不会阻止该变量或者方法被实例对象访问。
后置单下划线,例如var_
有的时候属性名或者方法名与某些系统关键字重复,这个时候就可以在后面加下划线来解决名字冲突。
看下示例。
In [7]: def test(name,class): pass
File "<ipython-input-7-d40cadd3ccf1>", line 1
def test(name,class): pass
^
SyntaxError: invalid syntax
In [8]: def test(name,class_): pass
In [9]:
做为另一个PEP8中明确定义的写法,后置单下划线用于解决名称冲突的场景。这也是一种非强制性的约定俗成的写法。
前置双下划线,例如__var
这个需要重点说一说。
前面两种写法都是约定俗成的,但是这一次却不同。如果是前置双下划线的属性或方法,Python解释器会将该名字进行重写,以避免子类中出现不必要的名称冲突。这种操作被称为名字修饰(name mangling)。
看下示例。
修改上面的Test
类如下
class Test:
def __init__(self):
self.name = 'xiaofu'
self._englishname = 'victor'
self.__hobby = 'basketball'
def say(self):
print(self.name)
def _shout(self):
print(self._englishname)
def __play(self):
print(self.__hobby)
重新import一下,尝试去查看实例对象的所有的属性和方法
In [1]: import test_underscore
In [2]: xiaofu = test_underscore.Test()
In [3]: dir(xiaofu)
Out[3]:
['_Test__hobby',
'_Test__play',
'__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__',
'_englishname',
'_shout',
'name',
'say']
这里出现在最后的是我们自定义的四个属性和方法,但是前置双下划线的一个__hobby
属性和__play()
方法却不见了。如果仔细看,会发现在最上面出现了_Test__hobby
和_Test__play
这两个属性。
会不会这两个就是我们定义的属性和方法呢?我们来访问看看
In [5]: xiaofu._Test__hobby
Out[5]: 'basketball'
In [6]: xiaofu._Test__play()
basketball
发现它们俩就是我们自己定义的__hobby
和__play()
,只是Python帮我们对名字进行了重写,在前面加了一个类名前缀。所以前置双下划线的属性和方法是不可以直接被实例访问的,之所以加上“直接”,是因为Python只是改了个名字不让我们访问,用更改后的名字还是可以访问的。还是那句话,Python中没有私有和共有的概念,所以不能绝对让一个变量变为私有。
那么子类会如何去继承这种前置双下划线的属性和方法呢?
看下示例。
新建一个类来继承刚才的Test类
class ExtendedTest(Test):
pass
查看一下子类的实例包含的所有属性和方法
In [1]: import test_underscore
In [2]: xiaofu=test_underscore.ExtendedTest()
In [3]: dir(xiaofu)
Out[3]:
['_Test__hobby',
'_Test__play',
'__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__',
'_englishname',
'_shout',
'name',
'say']
也就是说父类中定义的__hobby
和__play()
也没有被直接继承,而是继承的_Test__hobby
和_Test__play()
。所以访问的话也是会失败的
In [4]: xiaofu.__hobby
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-16b62e458c02> in <module>
----> 1 xiaofu.__hobby
AttributeError: 'ExtendedTest' object has no attribute '__hobby'
所以总结起来,前置双下划线的变量不能被实例访问,也不会继承给子类。
前后双下划线,例如__var__
首先需要说明的是,虽然前后双下划线也包含前置双下划线,但是名字修饰并不会对其起作用。
修改下前面的Test类
class Test:
def __init__(self):
self.name = 'xiaofu'
self._englishname = 'victor'
self.__hobby = 'basketball'
self.__age__ = 99
def say(self):
print(self.name)
def _shout(self):
print(self._englishname)
def __play(self):
print(self.__hobby)
def __grow__(self):
print(self.__age__)
前后双下划线的属性和方法都可以正常访问
In [1]: import test_underscore
In [2]: xiaofu=test_underscore.Test()
In [3]: xiaofu.__age__
Out[3]: 99
In [4]: xiaofu.__grow__()
99
所以可以看到前后双下划线和普通前后都不带下划线的变量没有啥区别,确实是这样。不过前后双下划线的变量被预留做python中的特殊变量了,这些方法被称作魔术方法(magic methods),不建议开发中进行使用。
单下划线,也就是单独的_
也是一种约定俗成的用法,有的时候使用单下划线来表示一个后面不会被使用的临时变量,例如循环中的index
In [1]: for _ in range(3): print('hello xiaofu')
hello xiaofu
hello xiaofu
hello xiaofu
当然这也是一种约定俗成的写法,想要去访问变量的值也是可以的
In [2]: for _ in range(3): print(_)
0
1
2
还有一种用法是在交互式Python中,例如IPython,用单下划线表示上一次运算的结果
In [1]: 1+2
Out[1]: 3
In [2]: _+1
Out[2]: 4
In [3]: _+2
Out[3]: 6
但是这样子的前提是没有对_
进行赋值
In [2]: for _ in range(3): print(_)
0
1
2
In [3]: 1+2+3
Out[3]: 6
In [4]: _+1
Out[4]: 3
In [5]: _+1
Out[5]: 3
总结
下面这个表格对上面五种下划线的写法进行了一个总结
格式 | 例子 | 说明 |
---|---|---|
前置单下划线 | _var | 约定俗成的写法,非强制,表明该变量只用作内部使用 |
后置单下划线 | var_ | 约定俗成的写法,非强制,用来解决名字冲突 |
前置双下划线 | __var | 强制,Python通过名字修饰使得变量不能被外部访问和继承 |
前后双下划线 | __var__ | 约定俗成的写法,预留给特殊变量,不建议开发时使用 |
单下划线 | _ | 约定俗成的写法,非强制,用来表示临时变量或者交互式环境中的上一次计算的结果 |