告别内存泄漏!Python弱引用技术详解与实战
兄弟你们是不是也有那种,一边撸代码一边盯着内存曲线发呆的经历?我就前两天晚上在公司加班,老大突然在群里问,谁懂点Python内存泄漏的事儿——他那个数据爬虫脚本,每跑一阵内存就一直涨,最后直接被OOM杀掉了。搞得我本来还想着11点能回去,结果直接原地蹲到了凌晨。后来一看代码,哈,真不是啥罕见大bug,就是没用好弱引用,典型的引用循环+缓存残留,搞得垃圾回收(GC)怎么都回收不掉。今天就想跟你们随便聊聊Python的弱引用,那点破事。
生活里的内存泄漏,真挺闹心
其实说实话,Python按理来说,自动GC嘛,不像C++那么容易手滑出泄漏,但也别太信这套。有一回我们组那个小李,写了个大字典当缓存,结果用完也不删——你想啊,明面上没引用了,后台还留着,GC能知道?还有种更阴间的,循环引用:比如一个对象A里有个B,B又反过来引用A,这俩抱一起,GC就头大。python3虽然有循环检测,可碰到带__del__的对象还是有坑。
弱引用是个啥?怎么用?
那天我在公司楼下抽烟,跟朋友聊起来,说你们用过weakref没?其实就一个意思——弱引用不阻止对象被回收,你用它指着对象,对象没强引用了,照样说没就没。最常用的模块就是weakref,比如:
import weakrefclassCat:
pass
c = Cat()
r = weakref.ref(c)
print(r) # <weakref at ...; to 'Cat' at ...>
print(r()) # <__main__.Cat object at ...>
del c
print(r()) # None
你看,最后del c之后,r()直接就变None了,这玩意就像偷偷跟着对象,别人不爱了,自己就自杀,不拖泥带水。平时你搞缓存,比如做那种全局唯一对象池、图片对象缓存啥的,用weakref.WeakValueDictionary就特别香。它自己就能自动把不用的东西清理掉。
我前阵子帮人看一个WebSocket的长连接管理,就是有一堆Connection对象,原本是全丢一个list里,后来全员泄漏,谁断了都不清理。后来直接换成weakref搞:
import weakrefconnections = weakref.WeakSet()
classConnection:
def__init__(self, name):
self.name = name
for i in range(10):
c = Connection(f"conn-{i}")
connections.add(c)
print(len(connections)) # 10
# 假如有一部分connection下线
del c # 假装断开连接
print(len(connections)) # 实际上这时候只要对象没强引用,集合里自动去掉了
你用WeakSet维护活跃对象,Python自己帮你做“僵尸清理”,不用手动去一个个remove,真就是懒人福音。
不过话说回来,弱引用也不是万金油。有一次我同事写了个WeakValueDictionary做图片缓存,结果一不小心,图片没被任何强引用持有,全被GC清理了,缓存啥也没剩下……后来他还找我吐槽,说“为啥缓存都没了”。所以你要搞缓存,别全靠weakref,要有主引用顶着,weakref是锦上添花不是救命稻草。
还有一点,就是有的对象不能被弱引用,比如普通的int、str这种内置类型,你用weakref.ref(int(3)),直接报错TypeError,得用自定义对象。
别信什么“自动内存安全”
最后说一句,Python的自动GC很友好,但真不是你啥都不用管。像那天加班遇到的那种循环引用、缓存残留,真挺容易踩坑。weakref就是个小工具,合适场合用能省大事,但别全指望它。建议大家空了试试手写点弱引用Demo,别等线上崩溃才慌。
-END-
我为大家打造了一份RPA教程,完全免费:https://www.songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领