qt如何优化程序内存_Python基础教程:(小窍门)如何优化Python占用的内存

news/2025/2/25 5:29:45

概述

如果程序处理的数据比较多、比较复杂,那么在程序运行的时候,会占用大量的内存,当内存占用到达一定的数值,程序就有可能被操作系统终止,特别是在限制程序所使用的内存大小的场景,更容易发生问题。下面我就给出几个优化Python占用内存的几个方法。

说明:以下代码运行在Python3。

8211743ee22b4d3009a4b93a4991ce40.png

举个栗子

我们举个简单的场景,使用Python存储一个三维坐标数据,x,y,z。

Dict

使用Python内置的数据结构Dict来实现上述例子的需求很简单。

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

查看以下ob这个对象占用的内存大小:

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

简单的三个整数,占用的内存还真不少,想象以下,如果有大量的这样的数据要存储,会占用更大的内存。

6899b9a40946ec59d951807ac94546f2.png

Class

对于喜欢面向对象编程的程序员来说,更喜欢把数据包在一个class里。使用class使用同样需求:

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

class的数据结构和Dict区别就很大了,我们来看看这种情况下占用内存的情况:

050abead3a1c42c6054d6b4bfb410151.png

关于 _weakref_(弱引用)可以查看这个文档, 对象的__dict__中存储了一些self.xxx的一些东西。从Python 3.3开始,key使用了共享内存存储, 减少了RAM中实例跟踪的大小。

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

可以看到内存占用量,class比dict少了一些,但这远远不够。

_slots_

从class的内存占用分布上,我们可以发现,通过消除__dict__和_weakref__,可以显着减少RAM中类实例的大小,我们可以通过使用__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

可以看到内存占用显著的减少了

cbfa33dd29b9a8ca830c519c3bc49499.png

默认情况下,Python的新式类和经典类的实例都有一个dict来存储实例的属性。这在一般情况下还不错,而且非常灵活,乃至在程序中可以随意设置新的属性。但是,对一些在”编译”前就知道有几个固定属性的小class来说,这个dict就有点浪费内存了。

当需要创建大量实例的时候,这个问题变得尤为突出。一种解决方法是在新式类中定义一个__slots__属性。

__slots__声明中包含若干实例变量,并为每个实例预留恰好足够的空间来保存每个变量;这样Python就不会再使用dict,从而节省空间。

那么用slot就是非非常那个有必要吗?使用__slots__也是有副作用的:

  1. 每个继承的子类都要重新定义一遍__slots__
  2. 实例只能包含哪些在__slots__定义的属性,这对写程序的灵活性有影响,比如你由于某个原因新网给instance设置一个新的属性,比如instance.a = 1, 但是由于a不在__slots__里面就直接报错了,你得不断地去修改__slots__或者用其他方法迂回的解决
  3. 实例不能有弱引用(weakref)目标,否则要记得把__weakref__放进__slots__

最后,namedlist和attrs提供了自动创建带__slot__的类,感兴趣的可以试试看。

Tuple

Python还有一个内置类型元组,用于表示不可变数据结构。 元组是固定的结构或记录,但没有字段名称。 对于字段访问,使用字段索引。 在创建元组实例时,元组字段一次性与值对象关联:

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

元组的示例很简洁:

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

可以看只比__slot__多8byte:

809d7f0f1f971d903e2642068b8fad42.png

Namedtuple

通过namedtuple我们也可以实现通过key值来访问tuple里的元素:

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_y(self): return self[2] # def __new__(cls, x, y, z): return tuple.__new__(cls, (x, y, z))

此类的所有实例都具有与元组相同的内存占用。 大量实例会留下稍大的内存占用:

a6a286ee04ecfb11711be649596fd6c9.png

Recordclass

python的第三方库recordclassd提供了一个数据结构recordclass.mutabletuple,它几乎和内置tuple数据结构一致,但是占用更少的内存。

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

实例化以后,只少了PyGC_Head:

65b6b560f70cd83e3d215a90ab1e4636.png

到此,我们可以看到,和__slot__比,又进一步缩小了内存占用:

2267268e0c1169832fabbc8a56d833ce.png

Dataobject

recordclass提供了另外一个解决方法:在内存中使用与__slots__类相同的存储结构,但不参与循环垃圾收集机制。通过recordclass.make_dataclass可以创建出这样的实例:

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

另外一个方法是继承自dataobject

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

以这种方式创建的类将创建不参与循环垃圾收集机制的实例。 内存中实例的结构与__slots__的情况相同,但没有PyGC_Head:

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

要访问这些字段,还使用特殊描述符通过其从对象开头的偏移量来访问字段,这些对象位于类字典中:

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

Cython

有一种方法基于Cython的使用。 它的优点是字段可以采用C语言原子类型的值。例如:

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

内存结构分布如下:

5156ebd2d6b4d7fc0ffbbf63f27bd7c1.png

但是,从Python代码访问时,每次都会执行从int到Python对象的转换,反之亦然。

Numpy

在纯Python的环境中,使用Numpy能带来更好的效果,例如:

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

创建初始值是0的数组:

 >>> points = numpy.zeros(N, dtype=Point)
43065f8afef58262787bd8290a9137bb.png
27acf094a1d8262deb14a5213bb191de.png

最后

可以看出,在Python性能优化这方面,还是有很多事情可以做的。Python提供了方便的同时,也需要暂用较多的资源。在不通的场景下,我们需要选择不同的处理方法,以便带来更好的性能体验。


http://www.niftyadmin.cn/n/712988.html

相关文章

python生成表格乱码_Python程序中使用SQLAlchemy时出现乱码的解决方案

今天对clubot进行了升级, 但是导入数据后中文乱码, 一开是找资料说是在创建引擎的时候添加编码信息:?但是这并不行, 然后查看表信息:?发现原来创建表的时候用的latin1编码, 而老的表是用utf-8编码创建的, SQLAlchemy中并没有发现有创建表时指定指定编码的方法. 所以只能在MyS…

ZZULIOJ-1092,素数表(函数专题)(Python)

题目描述: 输入两个正整数m和n,输出m和n之间的所有素数。 要求程序定义一个prime()函数和一个main()函数,prime()函数判断一个整数n是否是素数,其余功能在main()函数中实现。 int prime(int n) { //判断n是否为素数&#xff0…

计算机硬件的组成要素

不清楚cpu(处理器)、内存、I/O的小伙伴们,重点来了! 计算机硬件的主要组成要素如下图:CPU是计算机的大脑,在其内部可对数据执行运算并控制内存和I/O。内存用于存储指令和数据。I/O负责把键盘、鼠标、显示器…

python 短路运算符_Python中三目计算符的正确用法及短路逻辑

今天在看别人代码时看到这样一种写法, 感觉是个挺容易踩到的坑, 搞清楚后写出来备忘.短路逻辑Python中进行逻辑运算的时候, 默认采用的是一种叫做短路逻辑的运算规则. 名字是很形象的, 下面直接看代码print True and 1# 1print True or 1# Trueprint False and 1# Falseprint F…

ZZULIOJ-1093,验证哥德巴赫猜想(函数专题)(Python)

题目描述: 哥德巴赫猜想大家都知道一点吧。我们现在不是想证明这个结论,而是对于任给的一个不小于6的偶数,来寻找和等于该偶数的所有素数对。做好了这件实事,就能说明这个猜想是成立的。 要求程序定义一个prime()函数和一个main()函数&#x…

ios多线程之GCD

介绍: Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以…

pandas 存xlsx多个标签_Pandas读写文件(1)

Pandas读写文件来源:https://realpython.com/pandas-read-write-files/翻译:老齐阅读本文需要10分钟★译者注: Pandas是数据科学中常用的库,用它读写文件也是常规操作,《数据准备和特征工程》一书对此有更详实的阐述。…

python进程线程协程面试_面试必备:进程,线程和协程

本文从操作系统原理出发结合代码实践讲解了以下内容:什么是进程,线程和协程?它们之间的关系是什么?为什么说Python中的多线程是伪多线程?不同的应用场景该如何选择技术方案?...什么是进程进程-操作系统提供…