写在之前
围绕类的话题,说是说不完的,仅在特殊方法,除了我们在前面遇到过的 __init__(),__new__(),__str__() 等之外还有很多。虽然它们只是在某些特殊的场景中才会用到,但是学会它们却可以成为你熟悉这门语言路上的铺路石。
所以我会在试图介绍一些「黑魔法」,让大家多多感受一下 Python 的魅力所在,俗话说「艺多不压身」就是这个道理了。
内存优化
首先先让我们从复习前面的类属性和实例属性的知识来引出另一个特殊方法:
>>> class Sample: ... name = 'rocky' ...
就像前面的文章我们所说的,每个类都有一个 __dict__() 属性,它包含了当前类的类属性:
>>> Sample.__dict__ mappingproxy({'__module__': '__main__', 'name': 'rocky', '__dict__': <attribute '__dict__' of 'Sample' objects>, '__weakref__': <attribute '__weakref__' of 'Sample' objects>, '__doc__': None}) >>> Sample.name 'rocky'
同样,如果我们创建了实例,每个实例也有一个 __dict__ 属性,它里面就是当前的实例属性:
>>> a = Sample() >>> a.__dict__ {} >>> a.age = 23 >>> a.__dict__ {'age': 23}
上面的操作可以看出,当实例刚刚创建的时候,__dict__ 是空的,只有创建了实例属性以后,它才包含其内容。实例的 __dict__ 和类的 __dict__ 是有所区别的,即实例属性和类属性是不同的。
从理论上来说,我们可以根据一个类创建无数的实例,新建一个实例以后,又创建了一个新的 __dict__,这将是一个很可怕的事情,虽然每个 __dict__ 所占的内存空间很小,当然这件事事实上是不会出现的。但是程序不能建立在这种不可靠的猜测的基础上,程序要对过程有明确的控制。
所以就要有一种方法能够控制 __dict__,于是「__slots__」应运而生。
>>> class Nature: ... __slots__ = ('tree','flower') ... >>> dir(Nature) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']
我们仔细来看 dir() 的结果,发现 __dict__ 属性没有了,也就是说 __slots__ 把 __dict__ 挤出去了,它进入了类的属性。
>>> Nature.__slot<p style="color:transparent">本文来源gao!daima.com搞$代!码网</p>s__ ('tree', 'flower')
从这里可以看出,类 Nature 有且仅有两个属性。从类的角度来看,其类属性只有这两个;从实例的角度来看,其实例属性也只有这两个。
>>> Nature.tree = 'liushu' >>> Nature.tree 'liushu' >>> Nature.tree = 'lishu' >>> Nature.tree 'lishu'
通过类可以对属性进行赋值和修改,这个似乎和以前的类属性没有什么区别,别着急,继续往下看就看到区别了:
>>> x = Nature() >>> x.__slots__ ('tree', 'flower') >>> y = Nature() >>> y.__slots__ ('tree', 'flower') >>> id(x.__slots__) 4531629384 >>> id(y.__slots__) 4531629384
你看,实例化以后,实例的 __slots__ 和类的 __slots__ 完全一样,这跟前面的 __dict__ 大不一样了。并且我们建立了两个实例,结果发现两个实例的 __slots__ 在内存中居然是一个,或者可以说是增加实例时 __slots__ 并不增加。
>>> x.tree 'lishu' >>> y.tree 'lishu'