You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:54:25