在 Python 中变量和方法里经常见到单下划线和双下划线,比如常见 __future__, __all__, __version__, __author__。
from __future__ import print_function
import os
__all__ = ['function1', 'function2']
__version__ = '0.01'
__author__ = 'towardsdeeplearning.com'
print("Hello World")
再细分的话就是下面几种情况了,我在这里简单列举下:前置的单下划线:_var
后置的单下划线:var_
前置的双下划线:__var
前后置的双下划线:__var__
单独的下划线:_
1. Single Leading Underscore: “_var”
下划线前缀一般约定是为了提示其他程序员,以单个下划线开头的变量或方法供内部使用。PEP 8 中定义了此约定,这是最常用的 Python 编程规范。当然,这个只是一个指示性,并不是强制,Python 在“私有”和“公共”变量之间没有像 Java 明确。
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
>>> t = Test()
>>> t.foo
11
>>> t._bar
23
这里即使加上了下划线的前缀,我们也能访问变量,但要注意的是,下划线的前缀会影响从模块导入名称的方式。
# 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"
这主要是因为通过通配符导从模块中导入所有函数,Python 将不会导入带下划线的函数(或者你显式的在__all__ 列表里定义),应避免使用通配符导入。但是常规导入不受前缀下划线命名约定的影响:
>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42
2.Single Trailing Underscore: “var_”
当一个属性名恰好跟 Python 的关键字重名,为了直观,可以在属性名后加个_
>>> def make_object(name, class):
SyntaxError: "invalid syntax"
>>> def make_object(name, class_):
... pass
3.双下划线前缀:“__var”
此时 Python 解释器会重写属性名称垃圾避免子类中命名冲突,这就是所谓的 mangling,名字修饰,解释器以某种方式更改变量的名称,以使以后扩展类时更难产生冲突。
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 42
使用 Python 内置的函数 dir() 查看下 Test 类的属性。
>>> 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() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。上边返回类里变量 foo, _bar, 但是没有发现 __baz 这个变量。
强行访问的话就会报错
>>> t = Test()
>>> t.__baz
AttributeError: "'Test' object has no attribute '__baz'"
我们仔细看下上边的列表,发现有个叫 _Test__baz 属性,这就是所谓的mangling ,这样做是为了防止变量在子类中被覆盖。
我们来建立个类 ExtendedTest,继承自 Test,试图在构造函数里覆盖已经存在的属性。
class ExtendedTest(Test):
def __init__(self):
super().__init__()
self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'
来看下 ExtendedTest 实例中 foo, _bar, 和 __baz 变量。
>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar 'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"
当试图访问 t2.__baz 时,又出现了 AttributeError,这其实就是 mangling,查看下属性确实没有__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']
__baz 变成了 _ExtendedTest__baz,基类 Test 的变量变成了 _Test__baz,我们试图访问下:
>>> t2._ExtendedTest__baz 'overridden'
>>> t2._Test__baz
42
那么对于方法来说,mangling 同样适用。
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
4. 前后双下划线
常用于 __init__, __call__,__iter__, __next__这些方法里,但是,通常我们自己的方法名最好不要用。
class PrefixPostfixTest:
def __init__(self):
self.__bam__ = 42
>>> PrefixPostfixTest().__bam__
42
5. Single Underscore: “_”
有时候函数返回值不止一个,但有些变量我们不需要,就可以使用 _ 来当个用不到的变量。
>>> for _ in range(32):
... print('Hello, World.')