千家信息网

如何用Python代码减少Python所需的内存

发表于:2025-01-16 作者:千家信息网编辑
千家信息网最后更新 2025年01月16日,如何用Python代码减少Python所需的内存,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。在执行程序时,如果内存中有
千家信息网最后更新 2025年01月16日如何用Python代码减少Python所需的内存

如何用Python代码减少Python所需的内存,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

在执行程序时,如果内存中有大量活动的对象,就可能出现内存问题,尤其是在可用内存总量有限的情况下。在本文中,我们将讨论缩小对象的方法,大幅减少 Python 所需的内存。

为了简便起见,我们以一个表示点的 Python 结构为例,它包括 x、y、z 坐标值,坐标值可以通过名称访问。

Dict

在小型程序中,特别是在脚本中,使用 Python 自带的 dict 来表示结构信息非常简单方便:

>>> ob = {'x':1, 'y':2, 'z':3}>>> x = ob['x']>>> ob['y'] = y

由于在 Python 3.6 中 dict 的实现采用了一组有序键,因此其结构更为紧凑,更深得人心。但是,让我们看看 dict 在内容中占用的空间大小:

>>> print(sys.getsizeof(ob))240

如上所示,dict 占用了大量内存,尤其是如果突然虚需要创建大量实例时:

类实例

有些人希望将所有东西都封装到类中,他们更喜欢将结构定义为可以通过属性名访问的类:

class Point: # def __init__(self, x, y, z): self.x = x self.y = y self.z = z>>> ob = Point(1,2,3)>>> x = ob.x>>> ob.y = y

类实例的结构很有趣:

在上表中,__weakref__ 是该列表的引用,称之为到该对象的弱引用(weak reference);字段 __dict__ 是该类的实例字典的引用,其中包含实例属性的值(注意在 64-bit 引用平台中占用 8 字节)。从 Python 3.3 开始,所有类实例的字典的键都存储在共享空间中。这样就减少了内存中实例的大小:

>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__)) 56 112

因此,大量类实例在内存中占用的空间少于常规字典(dict):

不难看出,由于实例的字典很大,所以实例依然占用了大量内存。

带有 __slots__ 的类实例

为了大幅降低内存中类实例的大小,我们可以考虑干掉 __dict__ 和__weakref__。为此,我们可以借助 __slots__:

class Point: __slots__ = 'x', 'y', 'z' def __init__(self, x, y, z): self.x = x self.y = y self.z = z>>> ob = Point(1,2,3)>>> print(sys.getsizeof(ob))64

如此一来,内存中的对象就明显变小了:

在类的定义中使用了 __slots__ 以后,大量实例占据的内存就明显减少了:

实例数

目前,这是降低类实例占用内存的主要方式。

这种方式减少内存的原理为:在内存中,对象的标题后面存储的是对象的引用(即属性值),访问这些属性值可以使用类字典中的特殊描述符:

>>> pprint(Point.__dict__)mappingproxy( .................................... 'x': , 'y': , 'z': })

为了自动化使用 __slots__ 创建类的过程,你可以使用库namedlist(https://pypi.org/project/namedlist)。namedlist.namedlist 函数可以创建带有 __slots__ 的类:

>>> Point = namedlist('Point', ('x', 'y', 'z'))

还有一个包 attrs(https://pypi.org/project/attrs),无论使用或不使用 __slots__ 都可以利用这个包自动创建类。

元组

Python 还有一个自带的元组(tuple)类型,代表不可修改的数据结构。元组是固定的结构或记录,但它不包含字段名称。你可以利用字段索引访问元组的字段。在创建元组实例时,元组的字段会一次性关联到值对象:

>>> ob = (1,2,3)>>> x = ob[0]>>> ob[1] = y # ERROR

元组实例非常紧凑:

>>> print(sys.getsizeof(ob))72

由于内存中的元组还包含字段数,因此需要占据内存的 8 个字节,多于带有 __slots__ 的类:

命名元组

由于元组的使用非常广泛,所以终有一天你需要通过名称访问元组。为了满足这种需求,你可以使用模块 collections.namedtuple。

namedtuple 函数可以自动生成这种类:

>>> Point = namedtuple('Point', ('x', 'y', 'z'))

如上代码创建了元组的子类,其中还定义了通过名称访问字段的描述符。对于上述示例,访问方式如下:

 class Point(tuple): # @property def _get_x(self): return self[0] @property def _get_y(self): return self[1] @property def _get_z(self): return self[2] # def __new__(cls, x, y, z): return tuple.__new__(cls, (x, y, z))

这种类所有的实例所占用的内存与元组完全相同。但大量的实例占用的内存也会稍稍多一些:

记录类:不带循环 GC 的可变更命名元组

由于元组及其相应的命名元组类能够生成不可修改的对象,因此类似于 ob.x 的对象值不能再被赋予其他值,所以有时还需要可修改的命名元组。由于 Python 没有相当于元组且支持赋值的内置类型,因此人们想了许多办法。在这里我们讨论一下记录类(recordclass,https://pypi.org/project/recordclass),它在 StackoverFlow 上广受好评(https://stackoverflow.com/questions/29290359/existence-of-mutable-named-tuple-in)。

此外,它还可以将对象占用的内存量减少到与元组对象差不多的水平。

recordclass 包引入了类型 recordclass.mutabletuple,它几乎等价于元组,但它支持赋值。它会创建几乎与 namedtuple 完全一致的子类,但支持给属性赋新值(而不需要创建新的实例)。recordclass 函数与 namedtuple 函数类似,可以自动创建这些类:

 >>> Point = recordclass('Point', ('x', 'y', 'z')) >>> ob = Point(1, 2, 3)

类实例的结构也类似于 tuple,但没有 PyGC_Head:

在默认情况下,recordclass 函数会创建一个类,该类不参与垃圾回收机制。一般来说,namedtuple 和 recordclass 都可以生成表示记录或简单数据结构(即非递归结构)的类。在 Python 中正确使用这二者不会造成循环引用。因此,recordclass 生成的类实例默认情况下不包含 PyGC_Head 片段(这个片段是支持循环垃圾回收机制的必需字段,或者更准确地说,在创建类的 PyTypeObject 结构中,flags 字段默认情况下不会设置 Py_TPFLAGS_HAVE_GC 标志)。

大量实例占用的内存量要小于带有 __slots__ 的类实例:

dataobject

recordclass 库提出的另一个解决方案的基本想法为:内存结构采用与带 __slots__ 的类实例同样的结构,但不参与循环垃圾回收机制。这种类可以通过 recordclass.make_dataclass 函数生成:

>>> Point = make_dataclass('Point', ('x', 'y', 'z'))

这种方式创建的类默认会生成可修改的实例。

另一种方法是从 recordclass.dataobject 继承:

class Point(dataobject): x:int y:int z:int

这种方法创建的类实例不会参与循环垃圾回收机制。内存中实例的结构与带有 __slots__ 的类相同,但没有 PyGC_Head:

>>> ob = Point(1,2,3)>>> print(sys.getsizeof(ob))40

如果想访问字段,则需要使用特殊的描述符来表示从对象开头算起的偏移量,其位置位于类字典内:

mappingproxy({'__new__': , ....................................... 'x': , 'y': , 'z': })

大量实例占用的内存量在 CPython 实现中是最小的:

Cython

还有一个基于 Cython(https://cython.org/)的方案。该方案的优点是字段可以使用 C 语言的原子类型。访问字段的描述符可以通过纯 Python 创建。例如:

cdef class Python: cdef public int x, y, z def __init__(self, x, y, z): self.x = x self.y = y self.z = z

本例中实例占用的内存更小:

>>> ob = Point(1,2,3)>>> print(sys.getsizeof(ob))32

内存结构如下:

大量副本所占用的内存量也很小:

但是,需要记住在从 Python 代码访问时,每次访问都会引发 int 类型和 Python 对象之间的转换。

Numpy

使用拥有大量数据的多维数组或记录数组会占用大量内存。但是,为了有效地利用纯 Python 处理数据,你应该使用 Numpy 包提供的函数。

>>> Point = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])

一个拥有 N 个元素、初始化成零的数组可以通过下面的函数创建:

 >>> points = numpy.zeros(N, dtype=Point)

内存占用是最小的:

一般情况下,访问数组元素和行会引发 Python 对象与 C 语言 int 值之间的转换。如果从生成的数组中获取一行结果,其中包含一个元素,其内存就没那么紧凑了:

 >>> sys.getsizeof(points[0]) 68

因此,如上所述,在 Python 代码中需要使用 numpy 包提供的函数来处理数组。

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

内存 实例 结构 对象 字段 函数 生成 字典 数组 可以通过 属性 情况 类型 循环 支持 代码 名称 垃圾 数据 方式 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 新乡市汉唐网络技术有限公司 p2p种子搜索器服务器 厦门销售服务管理软件开发 网络安全整治的主要任务包括 中国根服务器在哪里 北京的互联网开发科技公司 vue开发微信小程序连接数据库 青岛羚羊网络技术服务有限公司 高并发访问数据库大文件 学数据库该看什么教材 财务软件主服务器坏了怎么办 威海惠明珠软件开发有限公司 迅雷美女25数据库 华安助学服务器开小差是什么意思 天地通信网络技术 网络安全和信息技术能力建设 网络技术与服务有限公司 岳阳软件开发报价 服务器功耗是什么 文件服务器 共享 区别 数据库与信息系统 期末考试 网络安全培训四个月能上岗么 卡巴斯基管理服务器安装 美国独立服务器洛杉矶cn2独立 通信网络技术的核心技术 erp服务器管理制度 form怎么提交数据库 宿州服务器机箱企业 中国idc服务器报告 恐龙岛里怎么看最近玩过的服务器
0