调用sys.exit()后Python模块对象内存损坏问题排查
问题分析:sys.exit()后模块对象损坏的间歇性问题
你遇到的这个间歇性问题,核心原因不是Python的bug,而是Python退出时全局对象销毁顺序的不确定性在搞鬼!让我们一步步拆解清楚:
问题复现代码
主程序 bug_happens.py
#!/usr/bin/python3 import sys import usbtmc scope = usbtmc.Instrument() print('in Main') print(' id of usbtmc.usb: 0x{:x}'.format(id(usbtmc.usb))) print(' id of usbtmc.usb.util: 0x{:x}'.format(id(usbtmc.usb.util))) print(' id of usbtmc.usb.util.dispose_resources: 0x{:x}'.format(id(usbtmc.usb.util.dispose_resources))) print() print('calling Exit()') sys.exit()
精简后的 usbtmc 模块
""" ****** Very stripped down partial Version for debugging a memory corruption issue ****** Python USBTMC driver Copyright (c) 2012-2017 Alex Forencich ... """ import usb.util class Instrument(object): def __init__(self, *args, **kwargs): self.connected = False print('usbtmc.Instrument created 0x{:x}'.format(id(self))) print(' id of usb: 0x{:x}'.format(id(usb))) print(' id of usb.util: 0x{:x}'.format(id(usb.util))) print(' id of usb.util.dispose_resources: 0x{:x}'.format(id(usb.util.dispose_resources))) print() def __del__(self): print('usbtmc.Instrument.__del__() called for 0x{:x}'.format(id(self))) print(' id of usb: 0x{:x}'.format(id(usb))) print(' id of usb.util: 0x{:x}'.format(id(usb.util))) print(' id of usb.util.dispose_resources: 0x{:x}'.format(id(usb.util.dispose_resources))) print(' end of usbtmc.Instrument.__del__()') print()
现象说明
正常运行输出
usbtmc.Instrument created 0x7fee5775d9b0 id of usb: 0x7fee562800e8 id of usb.util: 0x7fee562247c8 id of usb.util.dispose_resources: 0x7fee561ad048 in Main id of usbtmc.usb: 0x7fee562800e8 id of usbtmc.usb.util: 0x7fee562247c8 id of usbtmc.usb.util.dispose_resources: 0x7fee561ad048 calling Exit() usbtmc.Instrument.__del__() called for 0x7fee5775d9b0 id of usb: 0x7fee562800e8 id of usb.util: 0x7fee562247c8 id of usb.util.dispose_resources: 0x7fee561ad048 end of usbtmc.Instrument.__del__()
约50%概率出现的错误
usbtmc.Instrument created 0x7fec34b829b0 id of usb: 0x7fec336c3278 id of usb.util: 0x7fec335dbb88 id of usb.util.dispose_resources: 0x7fec335e5048 in Main id of usbtmc.usb: 0x7fec336c3278 id of usbtmc.usb.util: 0x7fec335dbb88 id of usbtmc.usb.util.dispose_resources: 0x7fec335e5048 calling Exit() usbtmc.Instrument.__del__() called for 0x7fec34b829b0 id of usb: 0xa40060 Exception ignored in: <bound method Instrument.__del__ of <usbtmc.Instrument object at 0x7fec34b829b0>> Traceback (most recent call last): File "/home/don/Electronics/Projects/HF_Meas/Scope_SA/Bug_usb_close/usbtmc.py", line 27, in __del__ AttributeError: 'NoneType' object has no attribute 'util'
偶尔出现的错误
usbtmc.Instrument created 0x7ff75968c9b0 id of usb: 0x7ff7581af0e8 id of usb.util: 0x7ff7581537c8 id of usb.util.dispose_resources: 0x7ff7580dc048 in Main id of usbtmc.usb: 0x7ff7581af0e8 id of usbtmc.usb.util: 0x7ff7581537c8 id of usbtmc.usb.util.dispose_resources: 0x7ff7580dc048 calling Exit() usbtmc.Instrument.__del__() called for 0x7ff75968c9b0 id of usb: 0x7ff7581af0e8 id of usb.util: 0xa40060 Exception ignored in: <bound method Instrument.__del__ of <usbtmc.Instrument object at 0x7ff75968c9b0>> Traceback (most recent call last): File "/home/don/Electronics/Projects/HF_Meas/Scope_SA/Bug_usb_close/usbtmc.py", line 28, in __del__ AttributeError: 'NoneType' object has no attribute 'dispose_resources'
问题根源
当你调用sys.exit()时,Python会触发解释器的退出流程:
- 首先,Python会开始清理全局命名空间中的对象,包括已导入的模块。
- 你的
Instrument实例scope是全局对象,它的__del__方法会在退出时被调用,但Python完全不保证全局对象的销毁顺序。 - 运气不好的时候,
__del__执行时,usb模块(或者usb.util子模块)已经被Python的退出清理机制销毁了——此时模块对象被置为None,内存被标记为无效,就出现了你看到的0xa40060这类垃圾值。
解决方案
你需要彻底避免在__del__方法中依赖全局模块对象,因为它们的生命周期完全不受你控制。推荐两种可靠的修复方式:
方案1:显式清理,替代__del__
添加一个显式的close()方法,在退出前手动调用,同时在实例中保存模块对象的引用,避免被提前销毁:
class Instrument(object): def __init__(self, *args, **kwargs): self.connected = False # 保存模块引用,绑定到实例生命周期 self.usb = usb self.usb_util = usb.util print('usbtmc.Instrument created 0x{:x}'.format(id(self))) print(' id of usb: 0x{:x}'.format(id(self.usb))) print(' id of usb.util: 0x{:x}'.format(id(self.usb_util))) print(' id of usb.util.dispose_resources: 0x{:x}'.format(id(self.usb_util.dispose_resources))) print() def close(self): print('usbtmc.Instrument.close() called for 0x{:x}'.format(id(self))) print(' id of usb: 0x{:x}'.format(id(self.usb))) print(' id of usb.util: 0x{:x}'.format(id(self.usb_util))) print(' id of usb.util.dispose_resources: 0x{:x}'.format(id(self.usb_util.dispose_resources))) print(' end of usbtmc.Instrument.close()') print() # 这里添加实际的资源释放逻辑 self.connected = False # 可选:保留__del__作为兜底,但要做防御性检查 def __del__(self): print('usbtmc.Instrument.__del__() called for 0x{:x}'.format(id(self))) # 先检查对象是否还存在 if hasattr(self, 'usb'): print(' id of usb: 0x{:x}'.format(id(self.usb))) else: print(' usb module already destroyed') if hasattr(self, 'usb_util'): print(' id of usb.util: 0x{:x}'.format(id(self.usb_util))) if hasattr(self.usb_util, 'dispose_resources'): print(' id of usb.util.dispose_resources: 0x{:x}'.format(id(self.usb_util.dispose_resources))) else: print(' dispose_resources already destroyed') else: print(' usb.util module already destroyed') print(' end of usbtmc.Instrument.__del__()') print()
主程序中手动调用close():
#!/usr/bin/python3 import sys import usbtmc scope = usbtmc.Instrument() print('in Main') print(' id of usbtmc.usb: 0x{:x}'.format(id(usbtmc.usb))) print(' id of usbtmc.usb.util: 0x{:x}'.format(id(usbtmc.usb.util))) print(' id of usbtmc.usb.util.dispose_resources: 0x{:x}'.format(id(usbtmc.usb.util.dispose_resources))) print() print('calling close()') scope.close() print('calling Exit()') sys.exit()
方案2:使用上下文管理器(推荐)
让Instrument实现上下文管理器协议,用with语句自动管理资源,这是Python中最优雅的资源管理方式:
class Instrument(object): def __init__(self, *args, **kwargs): self.connected = False self.usb = usb self.usb_util = usb.util print('usbtmc.Instrument created 0x{:x}'.format(id(self))) # ... 其他初始化逻辑 def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): # 这里执行资源清理逻辑 print('usbtmc.Instrument.__exit__() called for 0x{:x}'.format(id(self))) # ... 释放USB资源的代码 self.connected = False
主程序使用方式:
#!/usr/bin/python3 import sys import usbtmc with usbtmc.Instrument() as scope: print('in Main') # ... 你的业务逻辑代码 print('calling Exit()') sys.exit()
关键总结
- Python退出时的全局对象销毁顺序是完全不确定的,永远不要在
__del__中依赖全局模块或其他全局对象。 - 显式的资源清理(
close()方法)或上下文管理器是更可靠的资源管理方式,能彻底避免这类间歇性问题。 - 你看到的“垃圾值”不是内存损坏,而是模块对象被销毁后内存被标记为无效的表现,本质是生命周期管理问题。
内容的提问来源于stack exchange,提问作者Don M R
相关产品推荐
相关产品推荐

