学习python的时候,我们经常碰到一些变量的变量名带单下划线/双下划线前缀或者后缀,然后会觉得这样的命名风格很奇怪。除了变量命名风格外,也会遇到下划线的表达式,也会让人摸不着头脑。今天我们就来看下,python中下划线的作用。

忽略值

这是我们经常遇到的情况,比如调用某个函数,该函数会返回一组值,可是我们想要的返回值可能只是里面的某个,比如某函数除了返回关键属性值外还会返回一个状态码,而程序里可能只要状态码,那么可以这样

def get_value():
'''do something'''
return value, stat_code
_, stat_code = get_value()
if stat_code:
''' implement'''

你可以把不需要的值赋值给下划线_

解释器里上个表达式的值

在解释器中,这里的下划线保存了上个表达式的值

>>> 1 + 2
3
>>> _ * 2
6

私有变量

python里并没有关键字去定义类的成员方法和成员变量是私有或者公有的,不像Java里有private,public或者protected。python里做法是在给变量命名的时候添加一个下划线前缀,然而你会发现即使做了这样的命名,但是解释器在运行程序时,如果外界直接调用对象私有成员是不会报错的,所以这也被称为弱私有,并非强制。但这个是一个约定俗称的规定,虽然不受解释器限制,但作为python程序员,一旦遇到这样的情况,你心里就应该清楚,我不应该在外面直接去对实例成员做操作,请参阅PEP8。

class User:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
u = User("hi")
print(u.get_name())
u._name = "hello"
print(u.get_name())

执行u._name = "hello"并没有报错,执行结果:

hi

hello

还有一种情况,你可以显示的看到这个私有变量命名风格的效果,那就是在某模块里定义一个私有变量后,在其他模块用 from module import *导入到当前空间里是无法导入的。不过如果不用通配符*,具体指定对象名,私有变量依然可以被导入。

避免与python关键字或者内置对象名冲突

用后缀单下划线可以避免与python关键字或者内置对象名冲突

比如

len_ = 10
Name mangling

网上很多文章说到双下划线前命名的变量才是私有变量,其实我觉得私有成员的声明还是靠命名规则约定而且并非解释器限制的单下划线“_”。“__”只是营造出了“私有成员”的效果而已。简单来说,python解释器会把类中的以“__”前缀的变量名重新定义(name mangling), 该变量名被将替换为“_Classname__variable”,其中“Classname”是当前类名. 这样也避免了从父类继承的子类中出现的命名冲突。有点拗口,我们看个例子

class User:
def __init__(self, name, pwd):
self._name = name
self.__pwd = pwd
def get_name(self):
return self._name
def get_pwd(self):
return self.__pwd
u = User("hi", "123456")
print(dir(u))

运行结果

['_User__pwd', '__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__', '_name', 'get_name', 'get_pwd']

你会发现,u对象成员变量__pwd被改名成了_User__pwd,当你从外面直接操作u.__pwd,当然会报没有这个字段的错误,所以也就造成了私有成员的效果,其实本质它被改名了,你仍然可以通过u._User__pwd去访问。

如果我们在写一个类去继承User,并在里面同样定义成员变量__pwd,看看会发生什么

class User:
def __init__(self, name, pwd):
self._name = name
self.__pwd = pwd
def get_name(self):
return self._name
def get_pwd(self):
return self.__pwd
class Guest(User):
def __init__(self, name, pwd):
super(Guest, self).__init__(name, pwd)
self.__pwd = pwd
u = User("hi", "123456")
print(dir(u))
g = Guest("hello", "1111")
print(dir(g))

运行结果

['_User__pwd', '__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__', '_name', 'get_name', 'get_pwd']

['_Guest__pwd', '_User__pwd', '__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__', '_name', 'get_name', 'get_pwd']

我们看到Guest对象的__pwd被重新改名叫_Guest__pwd。

Magic method

前缀后缀双下划线变量是python中的特殊成员,比如__init__,__new__,它们都有自己的特殊用处,所被称为magic method,所以定义自己的变量时不要使用前缀后缀下划线命名。