Matplotlib定时器使用困惑:未保存引用致段错误问题咨询
Segmentation Fault When Using Matplotlib Timer Without Saving Reference (MacOSX Backend)
我来帮你拆解这个问题的根源,以及给出支持多Figure实例的通用解决方案:
核心原因:垃圾回收与MacOSX后端的定时器实现冲突
你遇到的Segmentation Fault,本质是Python垃圾回收机制和MacOSX后端的C级定时器实现不兼容导致的:
- 在
_redraw_later_ok里,你把Timer对象存在了self.timer中,类实例持有了Timer的引用,Python垃圾回收器不会回收还有引用的对象,所以Timer能存活到回调执行完毕,自然不会出问题。 - 而
_redraw_later_not_ok里的timer是方法内的局部变量,方法执行完后这个变量就没有任何引用了。Python的垃圾回收会很快把这个Timer对象销毁,但MacOSX后端的定时器是基于系统C级定时器实现的——系统层面还在等待触发回调,但Python层面已经把Timer对象释放了。当定时器触发时,试图访问已经被回收的对象,直接就触发了Segmentation Fault。 - 至于TkAgg后端能正常运行,是因为它的定时器实现逻辑不同,内部可能会保留定时器的引用,或者垃圾回收时机和MacOSX后端不一样,但这只是后端差异,不是通用解决方案。
支持多Figure实例的通用解决方案
要实现多Figure兼容,你不需要把Timer绑定到单个类实例,而是可以为每个Figure单独维护Timer引用,这里提供两种可靠的方式:
方式1:将Timer绑定到Figure对象的私有属性
from matplotlib import pyplot as plt import numpy as np class DrawEventHandler: def __init__(self): x = np.linspace(0,1,10) y = np.sin(x) self.fig, self.ax = plt.subplots() self.ax.plot(x,y) self.ax.figure.canvas.mpl_connect('draw_event', self.update) def update(self, event = None): self._redraw_later_general(self.fig) def _redraw_later_general(self, fig): # 用下划线开头的私有属性绑定Timer,避免和Matplotlib内部属性冲突 fig._draw_update_timer = fig.canvas.new_timer(interval=10) fig._draw_update_timer.single_shot = True fig._draw_update_timer.add_callback(lambda : fig.canvas.draw_idle()) fig._draw_update_timer.start() if __name__ == '__main__': # 测试多Figure实例 handler1 = DrawEventHandler() handler2 = DrawEventHandler() plt.show()
方式2:用全局字典管理Figure与Timer的映射
from matplotlib import pyplot as plt import numpy as np # 全局字典,保存Figure到Timer的映射,避免垃圾回收 figure_timer_map = {} class DrawEventHandler: def __init__(self): x = np.linspace(0,1,10) y = np.sin(x) self.fig, self.ax = plt.subplots() self.ax.plot(x,y) self.ax.figure.canvas.mpl_connect('draw_event', self.update) def update(self, event = None): self._redraw_later_general(self.fig) def _redraw_later_general(self, fig): timer = fig.canvas.new_timer(interval=10) timer.single_shot = True # 回调执行后删除映射,避免内存泄漏 def safe_draw_callback(): fig.canvas.draw_idle() if fig in figure_timer_map: del figure_timer_map[fig] timer.add_callback(safe_draw_callback) timer.start() figure_timer_map[fig] = timer if __name__ == '__main__': handler1 = DrawEventHandler() handler2 = DrawEventHandler() plt.show()
额外注意事项
- 两种方式都能确保Timer对象在回调执行前不会被垃圾回收,完美适配多Figure场景。
- 如果用全局字典的方式,一定要在回调里删除映射,避免Figure关闭后还保留Timer引用导致内存泄漏。
- 这个问题仅在MacOSX后端出现,其他后端(比如Qt5Agg、GTK3Agg)的定时器实现逻辑不同,可能不会触发段错误,但为了代码的跨后端兼容性,建议始终保留Timer的引用。
内容的提问来源于stack exchange,提问作者Thomas Kühn
相关产品推荐
相关产品推荐

