memory management in python

张开发
2026/4/16 23:00:10 15 分钟阅读

分享文章

memory management in python
Python内存管理1. 引言内存管理不管你是用哪种编程语言这都是个绕不开的核心问题玩Python也一样。搞不定内存管理你的程序轻则跑得慢吞吞重则直接吃掉整个系统资源甚至抛出一个MemoryError错误给你看。在这篇文章里咱们就一起钻到Python内存管理的底层把那些核心概念、实用技巧、常见问题都捋一遍最后再分享几个“最佳实践”帮你写出更省内存的Python代码。2. 内容目录Python内存管理核心概念栈内存与堆内存引用计数垃圾回收怎么用变量赋值与内存分配内存视图弱引用常见问题与操作搞懂内存泄漏测量内存占用最佳实践建议选对数据结构重复利用对象用生成器来迭代总结3. Python内存管理核心概念3.1 栈内存与堆内存跟很多编程语言一样Python主要用了两种内存区域栈和堆。栈内存它主要用来存放局部变量和函数调用的一些信息。你每次调用一个函数这个函数里的局部变量就会被“压”到栈里。等函数执行完了这些变量就会自动从栈里“弹出”占用的内存也就释放了。举个例子defcalculate_sum():a5# 变量 a 在栈上b10# 变量 b 在栈上resultabprint(result)calculate_sum()# 函数执行完a, b, result 占的栈内存自动释放在这个代码里a、b、result都是在栈上的。calculate_sum一跑完它们占的内存就自动还给系统了。堆内存堆是一个更动态的内存区域Python里的对象都在这儿分配。像列表、字典还有你自己定义的类的实例都放在堆上。堆内存的回收主要靠垃圾回收器来操心当对象不再被使用时它负责清理。my_list[1,2,3,4]# 列表对象 [1,2,3,4] 是在堆上分配的这里的my_list这个列表对象就是存在堆上的。3.2 引用计数Python管理内存主要靠一个叫引用计数的机制。每个Python对象身上都有个“计数器”记录了有多少个引用你可以理解为“指针”或者“名字”正指着它。当一个对象的引用计数掉到0就说明没人再用它了这个对象会立即被销毁它占的内存也被收回。x{name:Alice,age:30}# 字典对象的引用计数是1 (x 指着它)yx# 现在引用计数变成2 (x 和 y 都指着它)delx# 引用计数减1变成1dely# 引用计数变成0字典对象被销毁内存回收3.3 垃圾回收光靠引用计数还不够Python还有个“备份”叫垃圾回收器。它的主要任务是找出并清理那些“引用计数不为0但实际已经没法被访问到”的对象尤其是那些互相指来指去形成“循环引用”的对象。classParent:def__init__(self):self.childNoneclassChild:def__init__(self,parent):self.parentparent pParent()cChild(p)p.childc# 这里p 和 c 互相引用形成了循环# 如果没垃圾回收就算你 del p, del c这两个对象也无法被释放垃圾回收器会定期跑一跑把这种循环引用给拆开把内存释放掉。4. 怎么用4.1 变量赋值与内存分配在Python里给变量赋值背后就是在为对象分配内存。对于像整数、字符串这种不可变对象Python会缓存一些常用的值省点内存。a256b256print(aisb)# 输出: True (小整数常被缓存)c1000d1000print(cisd)# 输出: False (大整数不一定被缓存)而对于像列表、字典这种可变对象你每创建一个新实例它就会分配一块新内存。list_a[1,2,3]list_b[1,2,3]print(list_aislist_b)# 输出: False内容一样但是两个不同的对象4.2 内存视图内存视图给你个机会让你能直接“偷看”到某个对象内部的数据而不用费劲去复制一份。处理大数据集的时候用这个能省下不少内存。importstruct# 假设有一串二进制数据binary_datastruct.pack(4B,10,20,30,40)# 打包4个无符号字节viewmemoryview(binary_data)print(view[0])# 输出: 10 (直接访问没复制数据)print(view[1])# 输出: 204.3 弱引用弱引用顾名思义就是“弱弱地”指着一个对象不会增加它的引用计数。这在你希望一个对象没有其他“强”引用时就立刻能被垃圾回收但又想在它活着的时候还能偶尔访问一下的场景下很有用。importweakrefclassDataPack:passdataDataPack()weak_dataweakref.ref(data)print(weak_data())# 能访问到 __main__.DataPack object at ...deldata# 删除强引用ifweak_data():print(weak_data())else:print(那个DataPack对象已经凉透了被垃圾回收了)5. 常见问题与操作5.1 搞懂内存泄漏内存泄漏就是程序用完一块内存后忘了还给系统结果这块内存就“丢”了。在Python里常见的内存泄漏“元凶”包括垃圾回收器没处理掉的循环引用或者是不小心一直拿着一个大对象的引用不放手。defleak_example():cache[]foriinrange(500):# 模拟每次循环生成一个很大的临时列表但一直追加到cache里temp_large_listlist(range(100000))cache.append(temp_large_list)# 函数结束cache列表本身还在它引用的所有大列表都无法被回收returncache my_cacheleak_example()# 调用完大量内存被my_cache拽着泄漏了5.2 测量内存占用想知道你写的代码到底吃了多少内存可以用专门的工具来“测量”一下。memory_profiler就是个挺好用的库。# 先安装pipinstallmemory_profilerfrommemory_profilerimportprofileprofiledefmemory_hungry_function():large_list[iforiinrange(800000)]# 搞个大列表totalsum(large_list)returntotalif__name____main__:memory_hungry_function()运行后它会打印出函数里每一行代码的内存占用变化情况一目了然。6. 最佳实践建议6.1 选对数据结构选个趁手的数据结构。比如你需要存一大堆唯一的元素还要频繁查找某个元素在不在里面那用set或者dict肯定比用list快得多内存效率也更高。# 要检查一个ID是否在大量数据里用set比用list快多了large_id_setset(range(50000))if30000inlarge_id_set:print(ID 30000 找到了)6.2 重复利用对象与其翻来覆去地创建新对象不如在可能的情况下重复利用已有的。比如往列表里添加元素如果你大概知道会有多少个可以先初始化好一个指定大小的列表再往里填。# 不好的方式反复append可能导致列表多次扩容result[]foriinrange(200):result.append(i*2)# 好一点的方式提前分配好位置result[0]*200foriinrange(200):result[i]i*26.3 用生成器来迭代生成器是一种处理大数据集时非常省内存的迭代方式。它不会一次性把所有数据都加载到内存里而是“按需生产”用到一个才生成一个。defnumber_generator(limit):一个生成器逐个产生数字而不是一次返回整个列表n0whilenlimit:yieldn n1# 使用生成器内存占用非常低即使limit是1000万fornuminnumber_generator(10_000_000):# 处理numifnum100:break7. 总结Python内存管理这事儿虽然有点复杂但绝对是每个想写好Python程序的人必须搞明白的。弄懂了栈、堆、引用计数、垃圾回收这些基础概念你就能写出更高效的代码。再配合上合适的用法比如内存视图、弱引用警惕内存泄漏学会用工具测量内存最后遵循我们提到的最佳实践选对数据结构、复用对象、多用生成器你就能确保自己的Python程序跑得又快又稳内存也用得明明白白。

更多文章