在实际工作中,我们可能会面临创建百万级别量实例的这种情况,比如在某流行社交网络中,定义了用户类 User(id, name, sex, status, ...),每当有一个用户上线的时候,就在服务器内创建一个 User 实例。
这样当在线人数多的时候,很容易就会产生百万千万级别的实例,内存的开销十分巨大,如何降低这些大量实例的内存空间成了我们亟待解决的问题。
这篇文章,我就介绍一种解决办法:定义类的 __slot__ 属性,用它来声明实例属性的列表,可以用来减少内存空间的目的。
首先,我们先定义一个普通的 User 类:
代码语言:javascript复制class User1:
def __init__(self, id, name, sex, status):
self.id = id
self.name = name
self.sex = sex
self.status = status
然后再定义一个带 __slot__ 的类:
代码语言:javascript复制class User2:
__slots__ = ['id', 'name', 'sex', 'status']
def __init__(self, id, name, sex, status):
self.id = id
self.name = name
self.sex = sex
self.status = status
接下来创建两个类的实例:
代码语言:javascript复制u1 = User1('01', 'rocky', '男', 1)
u2 = User1('02', 'leey', '男', 1)
我们已经知道 u1 比 u2 使用的内存多,我们可以这样来想,一定是 u1 比 u2 多了某些属性,我们分别来看一下 u1 和 u2 的属性:
代码语言:javascript复制['__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__', 'id', 'name', 'sex', 'status']
代码语言:javascript复制['__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__', 'id', 'name', 'sex', 'status']
乍一看好像差别不大,我们下面具体来看一下差别在哪:
代码语言:javascript复制set(dir(u1)) - set(dir(u2))
通过做集合的差集,我们得到 u1 和 u2 在属性上的具体差别:
代码语言:javascript复制{'__weakref__', '__dict__'}
在我们不使用弱引用的时候,__weakref__ 并不占用多少内存,那最终这个锅就要 __dict__ 来背了。
下面我们来看一下 __dict__:
代码语言:javascript复制u1.__dict__
输出结果如下所示:
代码语言:javascript复制{'id': '01', 'name': 'rocky', 'sex': '男', 'status': 1}
输出一个字典,在它内部我们发现了刚刚在类里定义的属性,这个字典就是为了实例动态绑定属性的一个字典,我们怎么动态绑定呢?比如我们现在没有 u1.level 这个属性,那么我们可以为它动态绑定一个 level 属性,比如 u1.level = 10,然后我们再来考察这个字典:
代码语言:javascript复制u1.__dict__
现在输出的结果为:
代码语言:javascript复制{'id': '01', 'name': 'rocky', 'sex': '男', 'status': 1, 'level': 10}
这样看到 level 进入到这个字典中。
这样一个动态绑定属性的特性,其实是以牺牲内存为代价的,因为这个 __dict__ 它本身是占用内存的,接下来我们来验证这件事情:
代码语言:javascript复制import sys
sys.getsizeof(u1.__dict__)
我们用 sys 模块下的 getsizeof 方法,它可以得到一个对象使用的内存:
代码语言:javascript复制112
我们可以看到这个字典占用了 112 的字节。反观 u2,它没有了 __dict__ 这个属性,我们想给它添加一个属性,也是被拒绝的。
代码语言:javascript复制u2.level = 10
显示的结果如下所示:
代码语言:javascript复制AttributeError: 'User2' object has no attribute 'level'