Matplotlib congela quando input () usado no Spyder

Windows 7. Se eu abrir um terminal ipython simples na linha de comando, eu posso digitar:

import matplotlib.pyplot as plt plt.plot([1, 2, 3, 4, 5]) plt.show(block=False) input("Hello ") 

Mas se eu fizer a mesma coisa no Spyder, assim que eu pedir a input do usuário, a janela do Matplotlib congelará, então eu não posso interagir com ela. Eu preciso interagir com o gráfico enquanto o prompt está sendo exibido.

Tanto no Spyder quanto no console simples, matplotlib.get_backend () retorna ‘Qt4Agg’

Edit: Para esclarecer, eu tenho matplotlib configurar onde ele aparece em sua própria janela, não incorporado como um PNG. (Eu tive que definir Backend: Automatic originalmente para obter esse comportamento)

Como um aparte, no Spyder, o enredo abre instantaneamente depois de plt.plot (). No console normal, ele só abre após o plt.show (). Além disso, se eu pressionar Ctrl-C depois de digitar a input () no Spyder, todo o console trava inesperadamente. Vs. no IPython, ele apenas aumenta o KeyboardInterrupt e retorna o controle para o console.

Edit: Exemplo ainda mais completo: Funciona no console do IPython, não no Spyder (congela). Quer mover o enredo ao redor, de acordo com a input do usuário.

 import matplotlib.pyplot as pl def anomaly_selection(indexes, fig, ax): selected = [] for i in range(0, len(indexes)): index = indexes[i] ax.set_xlim(index-100, index+100) ax.autoscale_view() fig.canvas.draw() print("[%d/%d] Index %d " % (i, len(indexes), index), end="") while True: response = input("Particle? ") if response == "y": selected.append(index) break elif response == "x": return selected elif response == "n": break fig, ax = pl.subplots(2, sharex=True) ax[0].plot([1, 2, 3, 4, 5]) # just pretend data pl.show(block=False) sel = anomaly_selection([100, 1000, 53000, 4300], fig, ax[0]) 

Muita edição: Acredito que isso seja um problema com o input () bloqueando o Qt. Minha solução alternativa, se esta questão não for tracionada, é construir uma janela Qt com o gráfico Matplotlib embutido nela e obter a input do teclado pela janela.

Depois de cavar muito mais, cheguei à conclusão de que você deveria estar fazendo uma GUI. Eu sugiro que você use PySide ou PyQt. Para que o matplotlib tenha uma janela gráfica, ele executa um loop de events. Qualquer clique ou movimento do mouse triggers um evento que aciona a parte gráfica para fazer alguma coisa. O problema com o script é que todo código é de nível superior; sugere que o código está sendo executado sequencialmente.

Quando você insere manualmente o código no console do ipython, ele funciona! Isso ocorre porque o ipython já iniciou um loop de events da GUI. Todo comando que você chama é tratado no loop de events, permitindo que outros events também ocorram.

Você deve criar uma GUI e declarar esse backend da GUI como o mesmo backend do matplotlib. Se você tiver um botão, acione a function anomaly_selection, em seguida, essa function está sendo executada em um thread separado e deverá permitir que você ainda interaja dentro da GUI.

Com um monte de mexer e se mexer pela maneira como você chama fucntions, você pode fazer com que a function thread_input funcione.

Felizmente, PySide e PyQt permitem que você faça uma chamada manualmente para processar events da GUI. Eu adicionei um método que pede input em um thread separado e faz um loop esperando por um resultado. Enquanto aguarda, informa a GUI para processar events. return_input método return_input funcione se você tiver PySide (ou PyQt) instalado e o estiver usando como backend do matplotlib.

 import threading def _get_input(msg, func): """Get input and run the function.""" value = input(msg) if func is not None: func(value) return value # end _get_input def thread_input(msg="", func=None): """Collect input from the user without blocking. Call the given function when the input has been received. Args: msg (str): Message to tell the user. func (function): Callback function that will be called when the user gives input. """ th = threading.Thread(target=_get_input, args=(msg, func)) th.daemon = True th.start() # end thread_input def return_input(msg=""): """Run the input method in a separate thread, and return the input.""" results = [] th = threading.Thread(target=_store_input, args=(msg, results)) th.daemon = True th.start() while len(results) == 0: QtGui.qApp.processEvents() time.sleep(0.1) return results[0] # end return_input if __name__ == "__main__": stop = [False] def stop_print(value): print(repr(value)) if value == "q": stop[0] = True return thread_input("Enter value:", stop_print) thread_input("Enter value:", stop_print) add = 0 while True: add += 1 if stop[0]: break print("Total value:", add) 

Este código parece funcionar para mim. Embora tenha me dado alguns problemas com o kernel do ipython.

 from matplotlib import pyplot as pl import threading def anomaly_selection(selected, indexes, fig, ax): for i in range(0, len(indexes)): index = indexes[i] ax.set_xlim(index-100, index+100) ax.autoscale_view() #fig.canvas.draw_idle() # Do not need because of pause print("[%d/%d] Index %d " % (i, len(indexes), index), end="") while True: response = input("Particle? ") if response == "y": selected.append(index) break elif response == "x": selected[0] = True return selected elif response == "n": break selected[0] = True return selected fig, ax = pl.subplots(2, sharex=True) ax[0].plot([1, 2, 3, 4, 5]) # just pretend data pl.show(block=False) sel = [False] th = threading.Thread(target=anomaly_selection, args=(sel, [100, 1000, 53000, 4300], fig, ax[0])) th.start() #sel = anomaly_selection([100, 1000, 53000, 4300], fig, ax[0]) while not sel[0]: pl.pause(1) th.join() 

Alguém que sabe melhor que eu, por favor poste uma resposta se possível. Eu sei muito pouco sobre Python / Scipy / Spyder

Este é um módulo kludgy que eu escrevi que impede que a janela do Matplotlib congele enquanto a input () está pendente) sob o Spyder.

Você deve chamar prompt_hack.start() antes e prompt_hack.finish() depois, e replace input() por prompt_hack.input()

prompt_hack.py

 import matplotlib.pyplot import time import threading # Super Hacky Way of Getting input() to work in Spyder with Matplotlib open # No efforts made towards thread saftey! prompt = False promptText = "" done = False waiting = False response = "" regular_input = input def threadfunc(): global prompt global done global waiting global response while not done: if prompt: prompt = False response = regular_input(promptText) waiting = True time.sleep(0.1) def input(text): global waiting global prompt global promptText promptText = text prompt = True while not waiting: matplotlib.pyplot.pause(0.01) waiting = False return response def start(): thread = threading.Thread(target = threadfunc) thread.start() def finish(): global done done = True