Python 中的 slots 魔法

在 Python 中,每个类都有实例属性。在默认的情况下,会用一个字典来保存一个对象的实例属性。但是,有时候这会浪费很多内存。尤其是在创建很多对象的时候(成千上万个),会非常消耗内存。这是因为 Python 不能在对象创建的时候,直接分配一个固定的内存来保存所有的属性。

但是,可以使用 __slots_ 来解决消耗内存的问题。__slots__ 会告诉 Python 编译器不要使用字典,而且只给一个固定集合的属性分配空间。可以看看下面两个例子:

  • 不使用 __slots__ :

    1
    2
    3
    4
    5
    6
    class Myclass(object):
    def __init__(self, name, identifier):
    self.name = name
    self.identifier = identifier

    ...
  • 使用 __slots__

    1
    2
    3
    4
    5
    6
    7
    class Myclass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
    self.name = name
    self.identifier = identifier

    ...

下面可以通过使用 Ipython 的扩展模块 ipython_memory_usage,来查看内存的使用情况。

首先,安装这个模块:

1
pip install ipython_memory_usage

安装成功后,在命令行窗口输入 ipython (提前已安装好 ipython 模块),来开启 IPython 模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
E:\Python>IPYTHON
Python 3.6.5 |Anaconda, Inc.| (default, Mar 29 2018, 13:32:41) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import ipython_memory_usage.ipython_memory_usage as imu

In [2]: imu.start_watching_memory()
In [2] used 0.3945 MiB RAM in 35.71s, peaked 0.00 MiB above current, total RAM usage 40.01 MiB

In [3]: from slots import Myclass as My1
In [3] used 0.0469 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 40.05 MiB

In [4]: num = 1024*256
In [4] used 0.0039 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 40.06 MiB

In [5]: x = [My1(1,1) for i in range(num)]
In [5] used 16.1797 MiB RAM in 0.28s, peaked 0.00 MiB above current, total RAM usage 56.24 MiB

In [6]: from no_slots import Myclass as My2
In [6] used -0.0039 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 56.23 MiB

In [7]: x = [My2(1,1) for i in range(num)]
In [7] used 28.6367 MiB RAM in 0.31s, peaked 0.00 MiB above current, total RAM usage 84.87 MiB

首先在 E:\Python 的目录下,创建 slots.pyno_slots.py 这两个文件,内容上面两个代码块的内容一样。然后再开始 Ipython 模式,运行查看个别的占用内存情况。可以发现,no_slots.py 的情况,占用了 28.6 MB;而 slots.py 的情况,占用了 16.2 MB。相比之下,减少了不少内存。