Python代码中下划线的含义
2017-06-04
内容意译文于下面文章, 水平有限,欢迎指正。 The Meaning of Underscores in Python Python学习群:278529278 (欢迎交流)
前言
刚学习python的时候,对代码中下划线的用法很迷惑,有单下划线,双下环线,有前缀的,后缀的,还有两个都有的。到底这些特殊的表达对代码会有那些影响呢? 单双下划线作用于变量名和方法名上,有些时候下划线的用法只是大家约定俗成的默契,有些用法则会收到python解释器的影响。 常见五种用法罗列如下:
- 开头单下划线:_var
- 结尾单下划线:var_
- 开头双下划线:__var
- 开头和结尾双下划线:
__var__
- 单一下划线:_
命名开头单下划线:_var
单下划线只是python社区一个约定俗成的规定,用来提醒程序员以单下划线开头的变量和方法仅供类内部使用,不是公共接口的一部分。 python中并没有严格的私有方法和公有方法,这里单下划线更像一个善意的提醒。
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
>>> t = Test()
>>> t.foo
11
>>> t._bar
23
单下划线不会阻止你访问内部变量,他不是强制,而是善意的提示,全靠自觉。
但是开头单下划线会对模块导入产生影响
# This is my_module.py:
def external_func():
return 23
def _internal_func():
return 42
>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
NameError: "name '_internal_func' is not defined"
当使用*
import的时候,python不会导入开头单下划线的变量和方法,除非这些内容在`all` 中被明确定义
当然也不建议使用这种导入方式
import单个模块,则不受影响
>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42
结尾单下划线:var_
有的时候,有些变量命被关键字所占用,这个时候在结尾添加一个单下划线,可以避免命名冲突,使用这些更为恰当的名字。 这在pep8中有明确的定义
>>> def make_object(name, class):
SyntaxError: "invalid syntax"
>>> def make_object(name, class_):
... pass
开头双下划线:__var
开头双下划线会使python解释器改变当前变量的名字(name mangling)从而避免子类中可能出现的命名冲突
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 23
>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_bar', 'foo']
使用dir()方法,我们可以得到一个对象的属性列表。我们可以列表中很轻松的找到 foo,_bar
,但是__baz
却好像消失了?
到底发生了什么? 其实__baz
并未消失,只是被解释器修改成了 _Test__baz
这样这个变量就不会在子类中被覆盖。
class ExtendedTest(Test):
def __init__(self):
super().__init__()
self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'
>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"
>>> dir(t2)
['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']
>>> t2._ExtendedTest__baz
'overridden'
>>> t2._Test__baz
42
子类中通过开头双下划线定义的变量依然不能对象实例直接访问,但是我们通过dir()方法发现,子类中定义的__baz
并未覆盖掉父类中__baz
他们分别是_ExtendedTest__baz
, _Test__baz
, 但是对内部实现来说,他们也是完全透明的,解释器并未做限制。
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'"
除了变量名,对方法名也是同样适用的
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
由于命名重置(name mangling)的存在,解释器在对双下划线开头变量做展开的时候,也会使用到命名空间里面的全局变量。
_MangledGlobal__mangled = 23
class MangledGlobal:
def test(self):
return __mangled
>>> MangledGlobal().test()
23
开头和结尾双下划线:__var__
开头和结尾双下划线的用法又被称为魔术方法,命名重置(name mangling)对它不生效,不会修改名称。
由于魔术方法通常是python中预留的一些方法,比如 __init__
__call__
, 可以复写这些方法,但是不建议添加一些自定义魔术方法,这个可能会和将来python的改动
相冲突
class PrefixPostfixTest:
def __init__(self):
self.__bam__ = 42
>>> PrefixPostfixTest().__bam__
42
单一下划线:_
单一下划线作为变量是另一个约定,通常用来表示不会被使用的临时变量,只是upack时候用来占位
>>> for _ in range(32):
... print('Hello, World.')
>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car
>>> color
'red'
>>> mileage
3812.4
>>> _
12
在python REPL解释器下,_
同时也是用来表示上次表达式结果的一个特殊变量,有点类似于shell中的 $?
。
>>> 20 + 3
23
>>> _
23
>>> print(_)
23
>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]
趣闻
双下划线(double underscores) 行话又被称为dunder(甘蔗) 究其原因 大概是程序员都很懒吧。 能用一个词,就不愿意发两个单词的音,另外 dunder 词结构上也是某种程度上double undersocres的简写。