当前位置: 首页 > news >正文

Python弱引用与内存泄漏防治

Python弱引用与内存泄漏防治

weakref模块提供了创建弱引用的能力。弱引用不增加对象的引用计数,当对象只剩下弱引用时,GC可以回收它。

weakref.ref是最基础的弱引用:

import weakref

class ExpensiveObject:
def __init__(self, name):
self.name = name
self.data = bytearray(1024 * 1024) # 1MB数据

def __repr__(self):
return f"ExpensiveObject({self.name})"

obj = ExpensiveObject("test")
ref = weakref.ref(obj)

print(ref()) # ExpensiveObject(test)
del obj # 删除强引用
print(ref()) # None

ref()返回引用的对象,如果对象已被回收则返回None。ref()的返回值必须检查是否为None,否则后续操作会出错。

weakref的回调函数在对象被回收时触发:

def cleanup(ref):
print(f"对象被回收了: {ref}")

obj = ExpensiveObject("monitored")
ref = weakref.ref(obj, cleanup)

del obj # 输出: 对象被回收了:

回调在对象引用计数归零(生命周期结束)时立即调用,在对象的__del__之前执行。回调的接收参数是弱引用对象本身,不是原始对象(原始对象已不存在)。

weakref.proxy创建代理对象,可以直接访问原对象的方法和属性,但不需要额外的()调用:

obj = ExpensiveObject("proxy")
proxy = weakref.proxy(obj)

print(proxy.name) # "proxy",直接访问属性
del obj
try:
print(proxy.name) # ReferenceError: weakly-referenced object no longer exists
except ReferenceError as e:
print(e)

proxy的缺点是访问时无法感知对象是否存活,只有实际访问时才会抛出ReferenceError。ref()则通过返回值是否为None明确告知对象状态。

WeakValueDictionary的典型应用是缓存:

import weakref

class ImageCache:
def __init__(self):
self._cache = weakref.WeakValueDictionary()

def get_image(self, path):
img = self._cache.get(path)
if img is not None:
print(f"缓存命中: {path}")
return img

print(f"加载图像: {path}")
img = Image(path)
self._cache[path] = img
return img

class Image:
def __init__(self, path):
self.path = path
self.data = f"image_data:{path}"

def render(self):
return f"rendering {self.data}"

cache = ImageCache()
img1 = cache.get_image("/photo.jpg")
img2 = cache.get_image("/photo.jpg") # 缓存命中

del img1 # 删除强引用
import gc
gc.collect()

img3 = cache.get_image("/photo.jpg") # 可能需要重新加载

WeakValueDictionary在值对象不再被外部持有时自动移除条目。不需要手动清理缓存中的过期条目。

但WeakValueDictionary有一个陷阱:键的存活期也依赖值。如果值已经被回收,键仍然存在,但访问返回None:

cache = weakref.WeakValueDictionary()
obj = ExpensiveObject("temp")
cache['key'] = obj
print(cache.get('key')) # ExpensiveObject(temp)
del obj
gc.collect()
print(cache.get('key')) # None

WeakKeyDictionary是另一种方向的弱引用字典:

class EventHandler:
def __init__(self):
self._handlers = weakref.WeakKeyDictionary()

def register(self, obj, callback):
self._handlers[obj] = callback

def notify(self, *args, **kwargs):
for obj, callback in list(self._handlers.items()):
if obj is not None:
callback(obj, *args, **kwargs)
# 字典自动清理已回收的对象

handler = EventHandler()
class Listener:
def on_event(self, data):
print(f"处理: {data}")

listener = Listener()
handler.register(listener, Listener.on_event)

handler.notify("test") # 调用listener.on_event
del listener
gc.collect()
handler.notify("test") # 不输出任何内容

WeakKeyDictionary在对象被回收时自动移除条目。常用于需要给对象附加元数据但又不想延长对象生命周期的场景。

weakref.WeakSet是弱引用集合:

class Service:
def __init__(self):
self._instances = weakref.WeakSet()

def track(self, obj):
self._instances.add(obj)

def active_count(self):
return len(self._instances)

service = Service()
for _ in range(10):
obj = ExpensiveObject("temp")
service.track(obj)
# obj在循环结束时可能被回收

import gc
gc.collect()
print(f"活跃实例: {service.active_count()}") # 可能少于10

WeakSet自动管理元素的生存期。元素被回收后自动从集合中移除,不需要手动清理。

使用finalize替代__del__进行资源清理:

import weakref
import tempfile
import os

class TempFileResource:
def __init__(self, suffix='.tmp'):
self.file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
self.path = self.file.name

def cleanup(path):
try:
if os.path.exists(path):
os.unlink(path)
print(f"清理临时文件: {path}")
except OSError:
pass

weakref.finalize(self, cleanup, self.path)

rsc = TempFileResource()
print(f"创建临时文件: {rsc.path}")
del rsc # finalize立即执行清理

finalize比__del__更可靠:它在对象被回收时保证执行,不会因为循环引用而受阻。并且finalize可以接收参数,不像__del__只能操作实例属性。

finalize的另一个优势是支持调用优先级。通过atexit注册的函数在进程退出时也能执行,确保资源释放:

import atexit
import weakref

class DatabaseConnection:
def __init__(self, conn_string):
self.conn_string = conn_string
self._connection = None

def close_connection(conn_str):
print(f"关闭数据库连接: {conn_str}")

self._finalizer = weakref.finalize(self, close_connection, conn_string)
atexit.register(self._finalizer)

db = DatabaseConnection("postgresql://localhost/db")
# 即使忘记显式关闭,进程退出时也会清理

atexit注册的finalize在Python进程正常退出时执行。注意:如果程序被SIGKILL杀死,atexit处理程序不会运行。

weakref.getweakrefcount和getweakrefs检查对象的弱引用状态:

obj = ExpensiveObject("weak_demo")
ref1 = weakref.ref(obj)
ref2 = weakref.ref(obj)

print(weakref.getweakrefcount(obj)) # 2
print(len(weakref.getweakrefs(obj))) # 2

这个接口在调试时有用,可以确认对象被创建了多少弱引用。

循环引用中__del__的问题可以通过weakref解决:

class Node:
def __init__(self):
self.parent = None
self.children = []

def add_child(self, child):
self.children.append(child)
child.parent = weakref.ref(self) # 使用弱引用

def __del__(self):
print(f"Node deleted: {id(self)}")

parent = Node()
child = Node()
parent.add_child(child)

del parent
del child
gc.collect() # 两个节点都被正确回收

如果Node.parent是强引用,parent和child形成循环引用。使用weakref.ref(self)打破循环,GC可以正常回收。

weakref的代理模式在实现观察者模式时很实用:

class Observable:
def __init__(self):
self._observers = weakref.WeakSet()

def attach(self, observer):
self._observers.add(observer)

def detach(self, observer):
self._observers.discard(observer)

def notify(self, data):
dead = []
for observer in self._observers:
if observer() is not None:
observer(data)
else:
dead.append(observer)
for obs in dead:
self._observers.discard(obs)

class Widget:
def update(self, data):
print(f"Widget updated: {data}")

observable = Observable()
widget = Widget()
observable.attach(widget.update)

observable.notify("event1") # 正常通知
del widget
gc.collect()
observable.notify("event2") # widget已被回收

WeakSet中的回调函数在widget被回收后自动移除,不会尝试调用已销毁的对象。

Python 3.10中weakref的优化:ref对象的哈希操作速度提升了约40%,因为内部实现从Python层面移到了C层面,不再需要Python函数调用的开销。

内置类型对弱引用的支持情况:

import weakref

class WithSlots:
__slots__ = ('x', '__weakref__')

obj = WithSlots()
ref = weakref.ref(obj) # OK,因为__slots__包含了__weakref__

class WithoutWeak:
__slots__ = ('x',)

obj2 = WithoutWeak()
ref2 = weakref.ref(obj2) # TypeError: cannot create weak reference to 'WithoutWeak' object

Python内置的list、dict、int、str等类型默认不支持弱引用(通过子类化可以):

class MyList(list):
pass

lst = MyList([1, 2, 3])
ref = weakref.ref(lst) # OK

自定义类默认支持弱引用(包含__weakref__预留槽位),但定义了__slots__后需要显式加入'__weakref__'。

弱引用在框架设计中的实际应用案例——信号系统:

class Signal:
def __init__(self):
self._receivers = {}

def connect(self, receiver, sender=None):
key = (sender, id(receiver))
self._receivers[key] = weakref.ref(receiver)

def send(self, sender=None, **kwargs):
for (s, _), ref in list(self._receivers.items()):
if s is sender:
receiver = ref()
if receiver is not None:
receiver(**kwargs)
else:
# 清理已回收的接收者
del self._receivers[(s, _)]

signal = Signal()
class Subscriber:
def __call__(self, **data):
print(f"收到信号: {data}")

sub = Subscriber()
signal.connect(sub)
signal.send(sender=None, message="hello")
del sub
gc.collect()
signal.send(sender=None, message="world") # 不会调用已回收的对象

这个信号模式的优点是订阅者的生命周期不受信号系统的影响。即使忘记取消订阅,也不会造成内存泄漏。

http://www.jsqmd.com/news/1027720/

相关文章:

  • Vue 3跨平台UI框架架构设计:uView-Plus企业级组件库解决方案
  • 干货:做耐老化低温后补式玻璃门公司价格解析 - myqiye
  • 如何在Python中实现Black-Litterman资产配置?终极实战指南
  • 深度学习中的线性代数:矩阵乘法、基变换与SVD实战指南
  • 2026年长沙彩金回收怎么选?官方甄选几家正规机构推荐指南 - 优质品牌商家
  • 医疗费用预测实战:临床逻辑驱动的可解释机器学习建模
  • 从零搭建个人AI助手:轻量化LLM部署与联网搜索实战
  • 用计算机视觉做体感游戏:实时姿态估计与Unity集成实战
  • 怪物猎人世界终极插件指南:HunterPie三步快速配置教程
  • NXP QorIQ USDPAA开发实战:用户空间数据平面加速核心原理与性能调优
  • 济南商河有CNAS认可实验室的标气供应商推荐 - myqiye
  • 2026年评价高的长沙罗汉松/小叶造型罗汉松/浏阳造型罗汉松庭院推荐 - 行业平台推荐
  • Sqribble:基于模板规则的轻量级文档操作系统解析
  • ColdFire V5核心架构解析:双发射超流水线如何实现嵌入式SoC性能跃迁
  • 2026年汕头生腌外卖实测:本地人私藏的8家平价海鲜店价格与口味全解析 - 优质品牌商家
  • 2026年口碑好的成都名匠装修/成都名匠装饰/新都名匠正规公司推荐 - 行业平台推荐
  • Mythos模型实现漏洞挖掘阶跃突破:从SWE-bench到CVE自动化发现
  • 2026年小商品城供应商甄选指南:官方平台与区域服务商的协同价值解析 - 优质品牌商家
  • 木蜡油批发哪里有培训服务?这份指南为你揭秘 - myqiye
  • 135、高通 ChromaTix 调优工具入门:参数含义、调优流程与效果对比
  • 嵌入式网络设备QMan PFDR内存配置与性能调优实战
  • 博客系统接口自动化测试实战:从Pytest框架到CI/CD集成
  • 2024-2026成人用品供应链全解析:从渠道地图到成本控制实战
  • 2026免费在线抠图工具保姆级教程!无水印手机电脑通用一键抠图指南
  • 遵义漏水检测维修权威推荐:卫生间-厨房-阳台-屋顶天花板漏水维修:靠谱防水补漏公司团队TOP5推荐(2026最新深度调研实测榜单) - 即刻修防水
  • 全连接层反向传播实现与梯度调试实战指南
  • NPS面板HTTPS加密实战:Nginx反向代理与原生配置深度对比
  • Java毕设选题推荐:基于 SpringBoot 的宠物寄养、护理综合服务系统设计 宠物机构数字化管理平台的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 2026年知名的堤坝防护石笼网/石笼网护坡/加筋石笼网/石笼网箱横向对比厂家推荐 - 品牌宣传支持者
  • 深入解析杜鹃算法:从技术原理到内容运营实战指南