Matplotlib动画结束后退出

13
我正在用Python编写一个程序,使用matplotlib来运行动画,其中包括数值解时间依赖的薛定谔方程。一切正常,但是一旦动画运行完毕,我希望窗口能够自动关闭。我的方法(如下所示)可以实现,但会抛出无法捕获的异常。虽然对于我需要做的事情来说它可以正常工作,但错误看起来非常混乱。
我有一种替代方法,可以在不抛出错误的情况下工作,但需要用户手动关闭窗口(这对我的目的是不可接受的)。请问有人能指出我哪里做错了,或者建议更好的选择吗?
以下是我的代码的相关部分简化版:
from matplotlib import animation as ani
from matplotlib import pyplot as plt

multiplier = 0
def get_data():         # some dummy data to animate
    x = range(-10, 11)
    global multiplier
    y = [multiplier * i for i in x]
    multiplier += 0.005
    return x, y

class Schrodinger_Solver(object):
    def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):

        self.num_frames = num_frames
        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
        self.p_line, = self.ax.plot([], [])

        self.ani = ani.FuncAnimation(self.fig, self.animate_frame,
                                     init_func = self.init_func,
                                     interval = 1, frames = self.num_frames,
                                     repeat = False, blit = True)

        plt.show()

    def animate_frame(self, framenum):
        data = get_data()
        self.p_line.set_data(data[0], data[1])

        if framenum == self.num_frames - 1:
            plt.close()
        # closes the window when the last frame is reached,
        # but exception is thrown. Comment out to avoid the error,
        # but then the window needs manual closing

        return self.p_line,

    def init_func(self):
        self.p_line.set_data([], [])
        return self.p_line,

Schrodinger_Solver()

我正在运行Python 2.7.2和matplotlib 1.1.0,使用的操作系统是Windows 7。

非常感谢您的帮助。

编辑: 以下是异常和跟踪信息:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in __call__
    return self.func(*args)
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 495, in callit
    func(*args)
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 116, in _on_timer
    TimerBase._on_timer(self)
  File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 1092, in _on_timer
    ret = func(*args, **kwargs)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 315, in _step
    still_going = Animation._step(self, *args)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 177, in _step
    self._draw_next_frame(framedata, self._blit)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 197, in _draw_next_frame
    self._post_draw(framedata, blit)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 220, in _post_draw
    self._blit_draw(self._drawn_artists, self._blit_cache)
  File "C:\Python27\lib\site-packages\matplotlib\animation.py", line 240, in _blit_draw
    ax.figure.canvas.blit(ax.bbox)
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 244, in blit
    tkagg.blit(self._tkphoto, self.renderer._renderer, bbox=bbox, colormode=2)
  File "C:\Python27\lib\site-packages\matplotlib\backends\tkagg.py", line 19, in blit
    tk.call("PyAggImagePhoto", photoimage, id(aggimage), colormode, id(bbox_array))
TclError: this isn't a Tk application

Traceback (most recent call last):
  File "C:\Python27\quicktest.py", line 44, in <module>
    Schrodinger_Solver()
  File "C:\Python27\quicktest.py", line 26, in __init__
    plt.show()
  File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 139, in show
    _show(*args, **kw)
  File "C:\Python27\lib\site-packages\matplotlib\backend_bases.py", line 109, in __call__
    self.mainloop()
  File "C:\Python27\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 69, in mainloop
    Tk.mainloop()
  File "C:\Python27\lib\lib-tk\Tkinter.py", line 325, in mainloop
    _default_root.tk.mainloop(n)
AttributeError: 'NoneType' object has no attribute 'tk'

我可以通过小的修改来捕获第二个异常AttributeError:

try: plt.show()
except AttributeError: pass

无论我尝试什么,第一部分TclError总是存在。

什么是异常?请同时包含回溯信息! - David Zwicker
3个回答

4

我几分钟前也遇到了同样的问题……原因是在FuncAnimationinterval值太低。你的代码尝试每1毫秒更新图形,速度相当快!(可能不需要1000 fps)。我尝试了interval=200,错误消失了……

希望对你有所帮助。


但是这为什么会成为一个问题呢?我可以使用多低的间隔值才能避免出现错误?似乎有一些任意的限制。我正在尝试绘制尽可能快速进行的实时计算结果(手动确保在 animate 函数返回之前至少经过0.1秒),所以我不想插入人为延迟。 - Stanley Bak

0

尝试使用:

if framenum == self.num_frames - 1:
   exit()

对我来说它有效...


1
这可以通过完全关闭程序来实现。我需要单独关闭窗口的原因是,我希望在动画窗口关闭后发生其他事情(其他动画、计算、先前计算的动画之间的比较)。关闭程序会打败这个目的。自动化这个过程的重点是,我可以让我的电脑处理大量的计算,并在我回来时完成它。如果我必须在每个动画之后重新启动,那么我可能还是要坚持我原来的做法。 - user2546207
为了让您了解我的想法,如果您将以下内容添加到代码底部,那么我想要的是第一次动画运行,然后关闭,然后第二次动画运行:multiplier = 0; Schrodinger_Solver() - user2546207

0

我遇到了完全相同的问题,通过创建另一个Animation类,我成功解决了这个问题。实际上,我进行了两个更改:

  1. 编写一个自定义类,覆盖_stop_step方法。
  2. 在更新函数中引发StopIteration错误,而不是使用plt.close。异常将被捕获,不会中断脚本。

以下是代码。

from matplotlib import animation as ani
from matplotlib import pyplot as plt

class FuncAnimationDisposable(ani.FuncAnimation):
    def __init__(self, fig, func, **kwargs):
        super().__init__(fig, func, **kwargs)
        
    def _step(self, *args):
        still_going = ani.Animation._step(self, *args)
        if not still_going and self.repeat:
            super()._init_draw()
            self.frame_seq = self.new_frame_seq()
            self.event_source.interval = self._repeat_delay
            return True
        elif (not still_going) and (not self.repeat):
            plt.close()  # this code stopped the window
            return False
        else:
            self.event_source.interval = self._interval
            return still_going
        
    def _stop(self, *args):
        # On stop we disconnect all of our events.
        if self._blit:
            self._fig.canvas.mpl_disconnect(self._resize_id)
        self._fig.canvas.mpl_disconnect(self._close_id)

        

multiplier = 0
def get_data():         # some dummy data to animate
    x = range(-10, 11)
    global multiplier
    y = [multiplier * i for i in x]
    multiplier += 0.005
    return x, y

class Schrodinger_Solver(object):
    def __init__(self, xlim = (-10, 10), ylim = (-10, 10), num_frames = 200):

        self.num_frames = num_frames
        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111, xlim = xlim, ylim = ylim)
        self.p_line, = self.ax.plot([], [])

        self.ani = FuncAnimationDisposable(self.fig, self.animate_frame,
                                     init_func = self.init_func,
                                     interval = 1, frames = self.num_frames,
                                     repeat = False, blit = True)

        plt.show()

    def animate_frame(self, framenum):
        data = get_data()
        self.p_line.set_data(data[0], data[1])

        if framenum == self.num_frames - 1:
            raise StopIteration  # instead of plt.close()
            
        return self.p_line,

    def init_func(self):
        self.p_line.set_data([], [])
        return self.p_line,

Schrodinger_Solver()
Schrodinger_Solver()

print(multiplier)

(此代码片段已在Python 3.7和matplotlib 3.4.2中进行了测试。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接