Закрытие объекта pyplot в Tkinter при работе с PdfPages приводит к остановке экземпляра Tkinter - PullRequest
0 голосов
/ 19 февраля 2019

Я извиняюсь за название, но я не мог придумать лучшего описания проблемы.У меня есть программа на основе Tkinter, у которой есть возможность для пользователя создать отчет в формате PDF, который состоит из обзора, за которым следуют некоторые подробные графики.Я знал, что по какой-то причине вся программа будет закрыта после завершения отчета в формате PDF, но я только недавно сел, чтобы действительно определить, что его вызвало.

Я обнаружил, что линия plt.close на начальном обзорном графике, вызывает ли закрытие всей программы после написания отчета в формате pdf (это первая часть, которую я не совсем точно понимаю, если виноват plot.close, почему весь модуль работает до завершения)?Во-вторых, почему это вообще происходит?

Минимальный пример, который я смог привести (с бессмысленными данными для графиков), приведен ниже, где, если строка, которой предшествует # THE CULPRIT, комментируется, *Экземпляр 1008 * остается активным, но если оставить его как есть, экземпляр Tk() будет закрыт.

import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path

class Pdf(object):
    def __init__(self, master):
        self.master = master
        pdf = PdfPages(Path.cwd() / 'demo.pdf')

        self.pdf = pdf

    def plot_initial(self):
        fig = plt.figure(figsize=(8,6))
        fig.add_subplot(111)

        mu, sigma = 0, 0.1
        s = np.random.normal(mu, sigma, 1000)
        count, bins, ignored = plt.hist(s, 30, density=True)
        plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
                 np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
                 linewidth=2, color='r')
        plt.title('Overview')
        plt.xlabel('X')
        plt.ylabel('Y')
        self.pdf.savefig(fig)
        # THE CULPRIT
        plt.close(fig)

    def plot_extra(self):
        fig = plt.figure(figsize=(8,6))
        fig.add_subplot(111)

        mu, sigma = 0, 0.1
        s = np.random.normal(mu, sigma, 1000)
        count, bins, ignored = plt.hist(s, 30, density=True)
        plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
                 np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
                 linewidth=2, color='r')
        plt.title('Extra')
        plt.xlabel('X')
        plt.ylabel('Y')
        self.pdf.savefig(fig)
        plt.close(fig)

    def close(self):
        self.pdf.close()

class MVE(object):
    @classmethod
    def run(cls):
        root = tk.Tk()
        MVE(root)
        root.mainloop()

    def __init__(self, master):
        self.root = master
        tk.Frame(master)

        menu = tk.Menu(master)
        master.config(menu=menu)

        test_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label='Bug', menu=test_menu)
        test_menu.add_command(label='PDF', command=
                              self.generate_pdf)

    def generate_pdf(self):
        pdf = Pdf(self)

        pdf.plot_initial()
        for i in range(0,3):
            pdf.plot_extra()
        pdf.close()

if __name__ == "__main__":
    MVE.run()

Версии установленных пакетов / база Python:

  • Python 3.7.0
  • Tkinter 8,6
  • Matplotlib 2.2.3
  • Numpy 1.15.1

Редактировать

Я обновил до Matplotlib 3.0.2 согласно предложению @ImportanceOfBeingErnest, однако проблема все еще остается.

Ответы [ 2 ]

0 голосов
/ 20 февраля 2019

Проблема возникает из-за того, что " напряжение в Matplotlib между предоставлением низкоуровневой библиотеки, которая будет использоваться разработчиками приложений, и непосредственным пользовательским интерфейсом. В этом случае тонкости, которые мы должны предоставить для конца-user (например, выход из основного цикла GUI, когда последняя фигура закрыта) конфликтует с тем, как @Tarskin хочет использовать Matplot в качестве библиотеки низкого уровня.".Полный комментарий см. здесь .

Проблема, по-видимому, вызвана matplotlib/lib/matplotlib/backends/_backend_tk.py:

def destroy(self, *args): 
    if self.window is not None: 
        #self.toolbar.destroy() 
        if self.canvas._idle_callback: 
            self.canvas._tkcanvas.after_cancel(self.canvas._idle_callback) 
        self.window.destroy() 
    if Gcf.get_num_fig_managers() == 0: 
        if self.window is not None: 
            self.window.quit() 
    self.window = None 

, которая закрывает приложение, когда все цифры закрыты.Это важно для работы plt.show(block=True) (поэтому, когда вы закрываете все участки, мы возвращаем управление в терминал).Для источника этого перейдите здесь .

Было предложено использовать Agg, и хотя это действительно исправляет это на данный момент, было предложено полностью игнорировать pyplot, когдаОдним из них является интеграция Matplotlib в автономный пакет.Поэтому теперь я исправил это, используя только класс matplotlib.figure.Figure, как указано ниже (с немного более бессмысленными данными, чтобы полностью избежать использования pyplot).

import tkinter as tk
from matplotlib.figure import Figure
import numpy as np
from matplotlib.backends.backend_pdf import PdfPages
from pathlib import Path

class Pdf(object):
    def __init__(self, master):
        self.master = master
        pdf = PdfPages(Path.cwd() / 'demo.pdf')
        fig = Figure(figsize=(8,6))
        axes = fig.add_subplot(111)
        axes.set_xlabel('X')
        axes.set_ylabel('Y')

        self.fig = fig
        self.axes = axes
        self.pdf = pdf

    def plot_initial(self):
        mu, sigma = 0, 0.1
        s = np.random.normal(mu, sigma, 1000)

        self.axes.clear()
        self.axes.plot(s)
        self.axes.set_title('Overview')
        self.pdf.savefig(self.fig)

    def plot_extra(self):
        mu, sigma = 0, 0.1
        s = np.random.normal(mu, sigma, 1000)

        self.axes.clear()
        self.axes.plot(s)
        self.axes.set_title('Extra')
        self.pdf.savefig(self.fig)

    def close(self):
        self.pdf.close()

class MVE(object):
    @classmethod
    def run(cls):
        root = tk.Tk()
        MVE(root)
        root.mainloop()

    def __init__(self, master):
        self.root = master
        tk.Frame(master)

        menu = tk.Menu(master)
        master.config(menu=menu)

        test_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label='Fixed', menu=test_menu)
        test_menu.add_command(label='PDF', command=
                              self.generate_pdf)

    def generate_pdf(self):
        pdf = Pdf(self)

        pdf.plot_initial()
        for i in range(0,3):
            pdf.plot_extra()
        pdf.close()

if __name__ == "__main__":
    MVE.run()

Полное обсуждениеможно найти на этой ветке выпуска github.

0 голосов
/ 20 февраля 2019

Похоже, что используемый бэкэнд по умолчанию - TkAgg, перед импортом matplotlib.pyplot:

import tkinter as tk
import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
...
измените его на неинтерактивный, например agg:
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...