__slots__是在python 2.2开始引入的一个新特性, 我们来看一下官方给出的解释.
This class variable can be assigned a string, iterable, or sequence of strings with variable names used by instances. If defined in a new-style class, __slots__ reserves space for the declared variables and prevents the automatic creation of __dict__ and__weakref__ for each instance.
为声明的字段保留空间, 不再为每个实例生成一个__dict__和__weakref__. [参考文献1]
python默认定义的class是可以动态添加属性的, 代码示例如下
1
2
3
4
5
6
7
8
9
10
|
In [ 1 ]: class
Test( object ):
...: pass
...:
In [ 2 ]: a =
Test()
In [ 3 ]: a.a =
1
In [ 4 ]: a.a
Out[ 4 ]: 1
|
可以动态地为a添加一个属性, 在class中会默认创建一个特殊的对象__dict__, 负责保存class的所有属性和方法.
这个__slots__主要的一个作用是减少了对内存的消耗, 这个尤其在对象数量较多的时候非常管用, 可以查看[参考文献3], 使用__slots__未作任何其他优化, 节省了9G内存.
示例代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import
sys
from
guppy import
hpy
class
Person_( object ):
__slots__ =
( "name" , "age" , "gender" )
def
__init__( self ):
pass
class
Person( object ):
def
__init__( self ):
pass
if
__name__ = =
"__main__" :
persons =
[]
for
i in
xrange ( 100000 ):
p =
Person()
p.name =
"name_%d"
%
i
p.age =
i
p.gender =
"female"
persons.append(p)
persons_ =
[]
for
i in
xrange ( 100000 ):
p =
Person_()
p.name =
"name_%d"
%
i
p.age =
i
p.gender =
"female"
persons_.append(p)
print
"size without slots: %d" %
sum ([sys.getsizeof(p) for
p in
persons])
print
"size of the __dict__ without slots: %d"
%
sum ([sys.getsizeof(p.__dict__) for
p in
persons])
print
"size of the __weakref__ without slots: %d"
%
sum ([sys.getsizeof(p.__weakref__) for
p in
persons])
print
"size with slots: %d" %
sum ([sys.getsizeof(p) for
p in
persons_])
h =
hpy()
print
h.heap()
|
程序输出结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
size without slots: 3200000
size of the __dict__ without slots: 14000000
size of the __weakref__ without slots: 800000
size with slots: 3600000
Partition of a set
of 739737
objects. Total size =
32889732
bytes.
Index Count %
Size %
Cumulative %
Kind ( class
/
dict
of class )
0
100000
14
14000000 43
14000000 43
dict
of __main__.Person
1
214673
29
7335252
22
21335252 65
str
2
100000
14
3600000
11
24935252 76
__main__.Person_
3
100000
14
3200000
10
28135252 86
__main__.Person
4
209137
28
2509644 8
30644896 93
int
5
181 0
831828 3
31476724 96
list
6
7627 1
339356 1
31816080 97
tuple
7
331 0
232004 1
32048084 97
dict
(no owner)
8
1670 0
120240 0
32168324 98
types.CodeType
9
73 0
110060 0
32278384 98
dict
of module
< 95
more rows. Type
e.g. ‘_.more‘
to view.>
|
获取内存大小的两种方法: 一种使用sys.getsizeof()方法, 一种可以采用第三方库guppy的hpy来查看, 上面的示例代码同时使用了这两种方式
Person是默认的new-style class, Person_是带有slots的new-style class. 为二者创建100000个对象, 来查看内存大小.
直接算Person_的大小总共为3.6M左右, Person的大小为3.2M. 我一直很奇怪这个为什么反而大了呢?
后来发现貌似__dict__的大小没有被算进去, Person.__dict__的总大小为14M, 这样Person的总大小为17.2M, 而Person_没有__dict__属性, 总大小为3.6M, 由此可以看出, slots可以节省非常多的内存.
__dict__一般的实现都是用空间换取时间, 所以本身的内存消耗非常大, 在对象数量越多的时候越明显.
大家如果在项目中发现python占用很大内存的时候, 可以考虑从这个角度去进行内存优化, 大部分情况是可以取得不错的效果.
水平有限, 欢迎拍砖!
参考文献
- 官方文档: http://docs.python.org/2.7/reference/datamodel.html?highlight=slots#__slots__
- slots的源码分析: http://www.kvmapp.com/program/python/python-slots.html
- 节省内存的实例: http://tech.oyster.com/save-ram-with-python-slots/
Python tricks(7) -- new-style class的__slots__属性,布布扣,bubuko.com