当前位置: 首页 > 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.rkmt.cn/news/1539599.html

相关文章:

  • Java毕业设计-基于 SpringBoot 的宠物之家综合管理系统的设计与实现 面向宠物服务场景的宠物之家管理平台设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 2026年长沙彩金回收怎么选?官方甄选几家正规机构推荐指南 - 优质品牌商家
  • Vue 3跨平台UI框架架构设计:uView-Plus企业级组件库解决方案
  • Sqribble:基于模板规则的轻量级文档操作系统解析
  • 2026年口碑好的成都名匠装修/成都名匠装饰/新都名匠正规公司推荐 - 行业平台推荐
  • Mythos模型实现漏洞挖掘阶跃突破:从SWE-bench到CVE自动化发现
  • 2026免费在线抠图工具保姆级教程!无水印手机电脑通用一键抠图指南
  • 全连接层反向传播实现与梯度调试实战指南
  • 2026年知名的堤坝防护石笼网/石笼网护坡/加筋石笼网/石笼网箱横向对比厂家推荐 - 品牌宣传支持者
  • NPS面板HTTPS加密实战:Nginx反向代理与原生配置深度对比
  • 2026年非古雪茄性价比口碑甄选:这几家专业渠道值得关注 - 优质品牌商家
  • 汽车电子虚拟平台技术:从SystemC建模到ESC系统开发实战
  • Python与VS Code开发环境搭建:从零配置到高效编程
  • 如何选择实木餐桌生产厂?潍坊柏喜林家具有限公司值得考虑 - myqiye
  • 2026年口碑好的盐城边坡加筋网/盐城河道加筋网精选推荐公司 - 品牌宣传支持者
  • Verilog 初学者福音:动态电路生成与实时交互功能
  • 2026年热门的长沙冬青/长沙造型红果冬青精品基地推荐 - 行业平台推荐
  • 深部矿井围岩失稳机理、监测预警与稳定性控制技术实战解析
  • 2026年有实力的江苏汽车零部件网带抛丸机/江苏双工位转台式抛丸机/热成形抛丸机涂油生产线/铝合金压铸抛丸机可靠供应商推荐 - 行业平台推荐
  • Linux下高效解压7z文件:从工具安装到自动化脚本全攻略
  • 2026年靠谱的龙门加工中心/长条型材加工中心/加工中心用户好评推荐 - 品牌宣传支持者
  • Excel Slicer深度设计:从筛选器到可交付分析组件
  • 2026年兰州工业滑升门市场观察:官方甄选五家值得关注的供应商评测 - 优质品牌商家
  • 2026年热门的热成形零件抛丸机/吊钩悬挂式抛丸机厂家哪家好 - 品牌宣传支持者
  • MOOTDX架构设计:构建高性能Python量化金融数据接口的工程实践
  • 当AI重构Java开发:会用智能体的工程师,正在赢麻了
  • 2026年可靠的广东三氯乙烯/广东阻燃清洗剂/广东环己烷/广东离子污染测试液可靠供应商推荐 - 行业平台推荐
  • Claude 3系列模型合规使用与提示工程实践指南
  • Shell脚本加密终极指南:3分钟掌握SHC保护技术
  • 2026年有实力的天车起重机/大连门式起重机/大连行车起重机/门式起重机品牌厂家推荐 - 行业平台推荐