跳转至

变量名称中的下划线


单下划线 _

表示临时的或无关紧要的变量, 例如:

for _ in range(5):
    print('Hello World!')

同时也可在REPL(交互式解释器)中储存最近的一个表达式的结果, 例如:

>>> 1+1
2
>>> _
2
>>> 1+2
3
>>> _
3
>>> list()
[]
>>> _.append(1)
>>> _.extend([2,3])
>>> _
[1, 2, 3]

前置单下划线 _var

单个下划线开头的变量或方法仅供内部使用, 在PEP 8中定义的约定俗成的非强制性的,不限制访问, 例如:

class Demo:
    def __init__(self):
        self.a = 1

    @staticmethod
    def _return_something():
        return 'Hello World'


demo = Demo()
print(demo.a)  # 输出1
print(demo._return_something())  # Hello World

但是在导入时会有一些区别, 例如编写如下模块:

"""demo.py"""


name = 'zhangsan'
_age = 18
gender = 'male'


def _get_gender():
    return gender


class _Birthday:
    BIRRTHDAY = '1970-01-01 00:00:00'

使用通配符导入模块中的变量, 默认会忽略以单下划线开头的变量、函数和类, 例如:

>>> from demo import *
>>> name
'zhangsan'
>>> age
18
>>> _gender
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_gender' is not defined
>>> _get_gender()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_get_gender' is not defined
>>> _Birthday
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_Birthday' is not defined

如果在模块demo.py中设置了__all__ = ['name', 'age', '_gender', '_get_gender', '_Birthday'], 则可以访问:

>>> from demo import *
>>> name
'zhangsan'
>>> age
18
>>> _gender
'male'
>>> _get_gender
<function _get_gender at 0x103291d80>
>>> _Birthday
<class 'a._Birthday'>

或者注释掉__all__但不使用通配符方式导入, 使用包名.的方式也可访问:

>>> import demo
>>> demo.name
'zhangsan'
>>> demo.age
18
>>> demo._get_gender
<function _get_gender at 0x102e29d80>
>>> demo._Birthday
<class 'a._Birthday'>

后置单下划线 var_

后置单下划线通常用于避免与Python的关键字产生冲突, 例如:

>>> def=1
  File "<stdin>", line 1
    def=1
       ^
SyntaxError: invalid syntax
>>> def_=1
>>> 
>>> def test(class):
  File "<stdin>", line 1
    def test(class):
             ^^^^^
SyntaxError: invalid syntax
>>> def test(class_):
... 

前置双下划线 __var

前置双下划线的变量、函数和类同单前置下划线, 例如:

"""demo.py"""


__a = 1


def __b():
    pass


class __C:
    pass
>>> from demo import *
>>> __a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__a' is not defined
>>> __b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__b' is not defined
>>> __C
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__C' is not defined
>>> import demo
>>> demo.__a
1
>>> demo.__b
<function __b at 0x100cf1d80>
>>> demo.__C
<class 'z.__C'>

但如果用于类内部的属性或方法及其方法变量, 会触发Python的一个特性: 名称修饰(name mangling), 即解释器会更改变量的名称, 例如:

"""demo.py"""


class Demo:
    __CLASSNAME = 'Demo'

    def __init__(self):
        self.foo = 1
        self._bar = 2
        self.__baz = 3

    def __hey(self):
        pass
>>> from demo import Demo
>>> d = Demo()
>>> d.__CLASSNAME
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Demo' object has no attribute '__CLASSNAME'. Did you mean: '_Demo__CLASSNAME'?
>>> d.foo
1
>>> d._bar
2
>>> d.__baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Demo' object has no attribute '__baz'. Did you mean: '_bar'?
>>> d.__hey
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Demo' object has no attribute '__hey'

无法访问__CLASSNAME、d._bazd.__hey, 就是因为名称修饰, 原本的名称已经被更改, 通过dir(d)`查看所有属性可以看出:

>>> dir(d)
['_Demo__CLASSNAME', '_Demo__baz', '_Demo__hey', '__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__', '_bar', 'foo']
  • __CLASSNAME被重命名成_Demo__CLASSNAME
  • __baz被重命名成_Demo__baz
  • __hey被重命名成_Demo__hey

所以实际上应该这样访问:

>>> d._Demo__CLASSNAME
'demo'
>>> d._Demo__baz
3
>>> d._Demo__hey
<bound method Demo.__hey of <demo.Demo object at 0x101110be0>>

即, 通过 _类名__变量名的方式可以访问, 但不建议这么做, 一般来讲前置双下划线的变量在约定俗成中时私有属性, 不允许外部访问

同时名称修饰会引发一个现象, 例如:

"""demo.py"""


class Demo:
    __CLASSNAME = 'Demo'

    def __init__(self):
        self.foo = 1
        self._bar = 2
        self.__baz = 3
        super().__init__()

    def __hey(self):
        pass


class SubDemo(Demo):
    __CLASSNAME = 'SubDemo'

    def __init__(self):
        self.foo = 11
        self._bar = 22
        self.__baz = 33
>>> from demo import SubDemo
>>> sd = SubDemo()
>>> dir(sd)
['_Demo__CLASSNAME', '_Demo__baz', '_Demo__hey', '_SubDemo__CLASSNAME', '_SubDemo__baz', '__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__', '_bar', 'foo']

存在 _Demo__CLASSNAME_Demo__baz_Demo__hey_SubDemo__CLASSNAME_SubDemo__baz

>>> sd._Demo__CLASSNAME
'Demo'
>>> sd._Demo__baz
3
>>> sd._Demo__hey
<bound method Demo.__hey of <demo.SubDemo object at 0x101045510>>
>>> sd._SubDemo__CLASSNAME
'SubDemo'
>>> sd._SubDemo__baz
33

证明: 类的私有属性可以被子类继承, 但因为名称修饰的机制导致不会被重写

在看一个奇怪的例子:

"""demo.py"""


_Demo__name = '全局变量Demo'


class Demo:
    @staticmethod
    def test():
        return __name

本案例中在IDE中一定会提示__name未定义, 属于一个错误

但是由于名称修饰的原因, 实际上不会返回__name, 而是返回修饰后_Demo__name

恰巧有个全局变量_Demo__name, 所以可以正常返回, 例如:

>>> from demo import Demo
>>> Demo().test()
'全局变量Demo'

前后双下划线 __var__

前后双下划线即原来的魔法(magic)变量或魔法方法, 现在叫dunder方法, 个人编码应避免此类写法