python使用__slots__让你的代码更加节省内存


Posted in Python onSeptember 05, 2018

前言

在默认情况下,Python的新类和旧类的实例都有一个字典来存储属性值。这对于那些没有实例属性的对象来说太浪费空间了,当需要创建大量实例的时候,这个问题变得尤为突出。

因此这种默认的做法可以通过在新式类中定义了一个__slots__属性从而得到了解决。__slots__声明中包含若干实例变量,并为每个实例预留恰好足够的空间来保存每个变量,因此没有为每个实例都创建一个字典,从而节省空间。

本文主要介绍了关于python使用__slots__让你的代码更加节省内存的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

现在来说说python中dict为什么比list浪费内存?

和list相比,dict 查找和插入的速度极快,不会随着key的增加而增加;dict需要占用大量的内存,内存浪费多。

而list查找和插入的时间随着元素的增加而增加;占用空间小,浪费的内存很少。

python解释器是Cpython,这两个数据结构应该对应C的哈希表和数组。因为哈希表需要额外内存记录映射关系,而数组只需要通过索引就能计算出下一个节点的位置,所以哈希表占用的内存比数组大,也就是dict比list占用的内存更大。

如果想更加详细了解,可以查看C的源代码。python官方链接:https://www.python.org/downloads/source/

如下代码是我从python官方截取的代码片段:

List 源码:

typedef struct {
 PyObject_VAR_HEAD
 /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
 PyObject **ob_item;
 
 /* ob_item contains space for 'allocated' elements. The number
 * currently in use is ob_size.
 * Invariants:
 * 0 <= ob_size <= allocated
 * len(list) == ob_size
 * ob_item == NULL implies ob_size == allocated == 0
 * list.sort() temporarily sets allocated to -1 to detect mutations.
 *
 * Items must normally not be NULL, except during construction when
 * the list is not yet visible outside the function that builds it.
 */
 Py_ssize_t allocated;
} PyListObject;

Dict源码:

/* PyDict_MINSIZE is the minimum size of a dictionary. This many slots are
 * allocated directly in the dict object (in the ma_smalltable member).
 * It must be a power of 2, and at least 4. 8 allows dicts with no more
 * than 5 active entries to live in ma_smalltable (and so avoid an
 * additional malloc); instrumentation suggested this suffices for the
 * majority of dicts (consisting mostly of usually-small instance dicts and
 * usually-small dicts created to pass keyword arguments).
 */
#define PyDict_MINSIZE 8
 
typedef struct {
 /* Cached hash code of me_key. Note that hash codes are C longs.
 * We have to use Py_ssize_t instead because dict_popitem() abuses
 * me_hash to hold a search finger.
 */
 Py_ssize_t me_hash;
 PyObject *me_key;
 PyObject *me_value;
} PyDictEntry;
 
/*
To ensure the lookup algorithm terminates, there must be at least one Unused
slot (NULL key) in the table.
The value ma_fill is the number of non-NULL keys (sum of Active and Dummy);
ma_used is the number of non-NULL, non-dummy keys (== the number of non-NULL
values == the number of Active items).
To avoid slowing down lookups on a near-full table, we resize the table when
it's two-thirds full.
*/
typedef struct _dictobject PyDictObject;
struct _dictobject {
 PyObject_HEAD
 Py_ssize_t ma_fill; /* # Active + # Dummy */
 Py_ssize_t ma_used; /* # Active */
 
 /* The table contains ma_mask + 1 slots, and that's a power of 2.
 * We store the mask instead of the size because the mask is more
 * frequently needed.
 */
 Py_ssize_t ma_mask;
 
 /* ma_table points to ma_smalltable for small tables, else to
 * additional malloc'ed memory. ma_table is never NULL! This rule
 * saves repeated runtime null-tests in the workhorse getitem and
 * setitem calls.
 */
 PyDictEntry *ma_table;
 PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);
 PyDictEntry ma_smalltable[PyDict_MINSIZE];
};

PyObject_HEAD 源码:

#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA  \
 struct _object *_ob_next;  \
 struct _object *_ob_prev;
 
#define _PyObject_EXTRA_INIT 0, 0,
 
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
 
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD   \
 _PyObject_HEAD_EXTRA  \
 Py_ssize_t ob_refcnt;  \
 struct _typeobject *ob_type;

PyObject_VAR_HEAD 源码:

/* PyObject_VAR_HEAD defines the initial segment of all variable-size
 * container objects. These end with a declaration of an array with 1
 * element, but enough space is malloc'ed so that the array actually
 * has room for ob_size elements. Note that ob_size is an element count,
 * not necessarily a byte count.
 */
#define PyObject_VAR_HEAD  \
 PyObject_HEAD   \
 Py_ssize_t ob_size; /* Number of items in variable part */

现在知道了dict为什么比list 占用的内存空间更大。接下来如何让你的类更加的节省内存。

其实有两种解决方案:

第一种是使用__slots__ ;另外一种是使用Collection.namedtuple 实现。

首先用标准的方式写一个类:

#!/usr/bin/env python

class Foobar(object):
 def __init__(self, x):
 self.x = x

@profile
def main():
 f = [Foobar(42) for i in range(1000000)]

if __name__ == "__main__":
 main()

然后,创建一个类Foobar(),然后实例化100W次。通过@profile查看内存使用情况。

运行结果:

python使用__slots__让你的代码更加节省内存

该代码共使用了372M内存。

接下来通过__slots__代码实现该代码:

#!/usr/bin/env python

class Foobar(object):
 __slots__ = 'x'
 def __init__(self, x):
 self.x = x
@profile
def main():
 f = [Foobar(42) for i in range(1000000)]

if __name__ == "__main__":
 main()

运行结果:

python使用__slots__让你的代码更加节省内存

使用__slots__使用了91M内存,比使用__dict__存储属性值节省了4倍。

其实使用collection模块的namedtuple也可以实现__slots__相同的功能。namedtuple其实就是继承自tuple,同时也因为__slots__的值被设置成了一个空tuple以避免创建__dict__。

看看collection是如何实现的:

python使用__slots__让你的代码更加节省内存

collection 和普通创建类方式相比,也节省了不少的内存。所在在确定类的属性值固定的情况下,可以使用__slots__方式对内存进行优化。但是这项技术不应该被滥用于静态类或者其他类似场合,那不是python程序的精神所在。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python爬取Coursera课程资源的详细过程
Nov 04 Python
Python代码调试的几种方法总结
Apr 15 Python
Python基于Socket实现的简单聊天程序示例
Aug 05 Python
在Python web中实现验证码图片代码分享
Nov 09 Python
Python基于opencv调用摄像头获取个人图片的实现方法
Feb 21 Python
Python实例方法、类方法、静态方法的区别与作用详解
Mar 25 Python
python微信聊天机器人改进版(定时或触发抓取天气预报、励志语录等,向好友推送)
Apr 25 Python
python向字符串中添加元素的实例方法
Jun 28 Python
python3.7 的新特性详解
Jul 25 Python
简单的Python人脸识别系统
Jul 14 Python
Python基础之元组与文件知识总结
May 19 Python
Python机器学习之基于Pytorch实现猫狗分类
Jun 08 Python
Python中__slots__属性介绍与基本使用方法
Sep 05 #Python
python使用xlrd和xlwt读写Excel文件的实例代码
Sep 05 #Python
python 3调用百度OCR API实现剪贴板文字识别
Sep 04 #Python
Python实现监控键盘鼠标操作示例【基于pyHook与pythoncom模块】
Sep 04 #Python
Python使用pyautogui模块实现自动化鼠标和键盘操作示例
Sep 04 #Python
Python PyAutoGUI模块控制鼠标和键盘实现自动化任务详解
Sep 04 #Python
selenium+python实现自动化登录的方法
Sep 04 #Python
You might like
php网页版聊天软件实现代码
2016/08/12 PHP
laravel unique验证、确认密码confirmed验证以及密码修改验证的方法
2019/10/16 PHP
JAVASCRIPT style 中visibility和display之间的区别
2010/01/22 Javascript
JavaScript 面向对象的 私有成员和公开成员
2010/05/13 Javascript
离开页面时检测表单元素是否被修改,提示保存的js代码
2010/08/25 Javascript
Javascript判断对象是否相等实现代码
2013/03/18 Javascript
nullJavascript中创建对象的五种方法实例
2013/05/07 Javascript
jquery遍历checkbox的注意事项说明
2014/02/21 Javascript
无刷新预览所选择的图片示例代码
2014/04/02 Javascript
jQuery添加/改变/移除CSS类及判断是否已经存在CSS
2014/08/20 Javascript
推荐10个2014年最佳的jQuery视频插件
2014/11/12 Javascript
JavaScript实现打开链接页面的方式汇总
2016/06/02 Javascript
vue输入框使用模糊搜索功能的实现代码
2020/05/26 Javascript
[03:15]2014DOTA2国际邀请赛 专访国士无双信心满满
2014/07/12 DOTA
400多行Python代码实现了一个FTP服务器
2012/05/10 Python
基于Python闭包及其作用域详解
2017/08/28 Python
python3.6下Numpy库下载与安装图文教程
2019/04/02 Python
浅谈pymysql查询语句中带有in时传递参数的问题
2020/06/05 Python
python删除指定列或多列单个或多个内容实例
2020/06/28 Python
国外最大的眼镜网站:Coastal
2017/08/09 全球购物
一个SQL面试题
2014/08/21 面试题
应届毕业生求职信
2013/11/30 职场文书
工程质量承诺书范文
2014/03/27 职场文书
给孩子的新年寄语
2014/04/08 职场文书
学校感恩教育活动总结
2014/07/07 职场文书
安全生产目标管理责任书
2014/07/25 职场文书
白鹤梁导游词
2015/02/06 职场文书
2015年师德师风自我评价范文
2015/03/05 职场文书
小学生五一劳动节演讲稿
2015/03/18 职场文书
工程项目合作意向书
2015/05/08 职场文书
2016党校学习心得体会范文
2016/01/07 职场文书
外出考察学习心得体会
2016/01/18 职场文书
2016年小学圣诞节活动总结
2016/03/31 职场文书
2016年第104个国际护士节活动总结
2016/04/06 职场文书
对Golang中的FORM相关字段理解
2021/05/02 Golang
Oracle用户管理及赋权
2022/04/24 Oracle