变量名称中的下划线
单下划线 _
表示临时的或无关紧要的变量, 例如:
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._baz和d.__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方法, 个人编码应避免此类写法