1.私有变量
在大多数面向对象的编程语言中,都存在着私有变量(private variable)的概念,所谓私有变量,就是指通过某种手段,使得对象中的属性或方法无法被外部所访问。
Python 对于私有变量的实现是引入了一种叫 name mangling 的机制(翻译过来叫 “名字改编”、“名称改写” 或者 “名称修饰”),语法是在变量名前面加上两个连续下划线(__):
>>> class C:
... def __init__(self, x):
... self.__x = x
... def set_x(self, x):
... self.__x = x
... def get_x(self):
... print(self.__x)
...
>>> c = C(250)
此时,我们是无法直接通过变量名访问到该变量的:
>>> c.__x
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
c.__x
AttributeError: 'C' object has no attribute '__x'
想要访问变量的值,就需要使用指定的接口,比如这段代码中的 set_x() 和 get_x() 方法:
>>> c.get_x()
250
>>> c.set_x(520)
>>> c.get_x()
520
2. name mangling 机制的实现原理
我们看看 dict 属性里面有啥:
>>> c.__dict__
{'_C__x': 250}
虽然这里面没有看到 __x,但是,却多了一个 _C__x 的属性对不对?
访问一下试试:
>>> c._C__x
520
果然如此……这个就是传说中的名字改编术!
做法其实也很简单,就是下横线(_)加上类名,再加上变量的名字。
方法名也是同样的道理:
>>> class D:
... def __func(self):
... print("Hello FishC.")
...
>>> d = D()
>>> d.__func()
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
d.__func()
AttributeError: 'D' object has no attribute '__func'
>>> d._D__func()
Hello FishC.
注意:name mangling 机制是发生在类实例化对象时候的事情,给对象动态添加属性则不会有同样的效果。
3. 不同数量的前缀下划线含义
不同数量的前缀下划线均有不同的特殊含义:
4. 效率的提升之道
Python 对象存储属性的工作原理 —— 字典(dict):
>>> class C:
... def __init__(self, x):
... self.x = x
...
>>> c = C(250)
>>> c.x
250
>>> c.__dict__
{'x': 250}
对象动态添加属性,就是将键值对添加到 dict 中:
>>> c.y = 520
>>> c.__dict__
{'x': 250, 'y': 520}
甚至你可以直接通过给字典添加键值对的形式来创建对象的属性:
>>> c.__dict__['z'] = 666
>>> c.__dict__
{'x': 250, 'y': 520, 'z': 666}
>>> c.z
666
但是,字典高效率的背后是以付出更多存储空间为代价的
如果我们明确知道一个类的对象设计出来,就只是需要那么固定的某几个属性,并且不需要有动态添加属性这样的功能,那么利用字典来存放属性,这种空间上的牺牲就是纯纯地浪费!
针对这个情况,Python 专门设计了一个 _ slots _ 类属性,避免了利用字典存放属性造成空间上的浪费。
举个例子:
>>> class C:
... __slots__ = ['x', 'y']
... def __init__(self, x):
... self.x = x
...
>>> c = C(250)
这样,我们就创建了一个属性受限制的对象。
访问 slots 中列举的属性是没问题的:
>>> c.x
250
>>> c.y = 520
>>> c.y
520
如果想要动态地添加一个属性,那就不好意思了:
>>> c.z = 100
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
c.z = 100
AttributeError: 'C' object has no attribute 'z'
这种限制不仅体现在动态添加属性上,如果在类的内部,想创建一个 _slots _ 不包含的属性,也是不被允许的:
>>> class D:
... __slots__ = ['x', 'y']
... def __init__(self, x, y, z):
... self.x = x
... self.y = y
... self.z = z
>>> d = D(3, 4, 5)
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
d = D(3, 4, 5)
File "<pyshell#6>", line 6, in __init__
self.z = z
AttributeError: 'D' object has no attribute 'z'
甚至是 dict 属性,也不存在了:
>>> d.__dict__
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
d.__dict__
AttributeError: 'D' object has no attribute '__dict__'
因为使用了 slots 属性,那么对象就会划分一个固定大小的空间来存放指定的属性,这时候 dict 属性就不需要了,空间也就节约了出来。
大家不要小看这点改变……
这里有位国外的老哥,仅仅由于将一个类的三个属性,都改为使用 slots 进行存储,立竿见影地直接节省了 9G 的内存空间
一行代码,竟然降低了 30% 的内存消耗,确实惊人!
不过这里有一点是需要特别强调的,就是使用 slots 属性的副作用其实也相当明显,那就是要以牺牲 Python 动态语言的灵活性,作为前提。
使用了 slots 属性,就没办法再拥有动态添加属性的功能了……
这可以说是它的一个副作用,但实际上很多开发者却利用这个副作用,来限制类属性的滥用。
最后,还有一点需要大家知道的是,继承自父类的 __ slots _ _ 属性是不会在子类中生效的,Python 只关注各个具体的类中定义的 _ _ slots __ 属性:
>>> class E(C):
... pass
...
>>> e = E(250)
>>> e.x
250
>>> e.y = 520
>>> e.z = 666
>>> e.__slots__
['x', 'y']
对象e虽然拥有 slots 属性,但它同时也拥有 dict 属性:
>>> e.__dict__
{'z': 666}