单下划线和双下划线在Python变量和方法名称中都有含义。其中一些含义仅仅是按照惯例,作为给程序员的提示,而另外一些则由Python解释器执行。
在本文中,主要讨论以下五种下划线模式和命名约定,以及它们如何影响Python程序的行为:
1.单前置下划线:_var
单下划线前缀是一个提示,即以单个下划线开头的变量或方法将用于内部使用。PEP 8中定义了这种约定。Python不像java对公共变量和私有变量有着严格的区分,单下划线前缀不会影响程序的行为。
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
>>> 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
如果使用通配符进行导入my_module中所有名称(在PEB 8中建议避免使用通配符的导入方式),则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
总结就是:单下划线是一种Python命名约定,指示名称用于内部使用。Python解释器通常不会强制执行它,只是作为对程序员的一个提示。
2.单后置下划线:var_
有时候一个变量最适合的名称已经被关键字所使用,在Python中类似def、class等关键字不能被用作变量名,可以在这些名称后面加单下划线以解决冲突。
>>> def make_object(name, class):
SyntaxError: "invalid syntax"
>>> def make_object(name, class_):
... pass
总之,单后置下划线是为了与Python关键字命名冲突而约定的。
3.双前置下划线:__var
前面两种命名模式都是一种约定,而双下划线前缀会导致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__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_bar', 'foo']
在这个列表里面没有__baz属性,但是可以看到有一个_Test__baz属性,这就叫做名称修饰,可以避免该属性被子类所覆盖。
现在创建一个类继承Test类,然后尝试覆盖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'"
在执行t2.__baz时出现了AttributeError,我们看一下t2里面的属性:
>>> 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和_Test__baz,_Test__baz是继承自Test对象的属性,而_ExtendedTest__ba则是t2里面通过名称修饰得到的属性。
>>> t2._ExtendedTest__baz
'overridden'
>>> 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'"
>>> ManglingTest()._ManglingTest__mangled
'hello'
名称修饰也会应用到方法上:
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
>>> MangledMethod()._MangledMethod__method()
42
下面是另一个名称修饰的例子:
_MangledGlobal__mangled = 23
class MangledGlobal:
def test(self):
return __mangled
>>> MangledGlobal().test()
23
在这里设置了一个全局变量_MangledGlobal__mangled,然后在创建一个MangledGlobal对象去调用test方法时,test方法返回的时_MangledGlobal__mangled,这说明名称修饰不是专门与类属性绑定的。它适用于类上下文中以两个下划线开头的任何名称。
4.双前置和后置下划线:__var__
名称修饰并不会作用于双下划线前缀和后缀的变量:
class PrefixPostfixTest:
def __init__(self):
self.__bam__ = 42
>>> PrefixPostfixTest().__bam__
42
双前置和后置下划线命名在Python语言中保留为特殊用途,这条规则包括如__init__用于对象构造函数或__call__使对象可调用。最好避免在自己的代码中使用双下划线前缀和后缀的名称,以防止未来Python语言的新变化。
5.单下划线:_
根据惯例,一个单独的下划线有时用作一个名称,表示变量是临时的或不重要的。
>>> for _ in range(32):
... print('Hello, World.')
可以在解包表达式时使用单个下划线作为不需要关心的变量,来忽略特定的值。同样,这个用法只是按照约定,在Python解释器中没有触发特殊的行为。单个下划线只是一个有效的变量名,有时用于此目的。
>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car
>>> color
'red'
>>> mileage
3812.4
>>> _
12
除了用作临时变量之外,在大多数Python REPLs中,“_”是一个特殊的变量,表示解释器对最后一个表达式求值的结果。
>>> 20 + 3
23
>>> _
23
>>> print(_)
23
>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]
Quick Summary:
译自:The Meaning of Underscores in Python