Figure interattive e programmazione asincrona #

Matplotlib supporta ricche figure interattive incorporando figure in una finestra GUI. Le interazioni di base della panoramica e dello zoom in un Axes per ispezionare i tuoi dati sono "incorporate" in Matplotlib. Ciò è supportato da un sistema completo di gestione degli eventi tramite mouse e tastiera che è possibile utilizzare per creare sofisticati grafici interattivi.

Questa guida intende essere un'introduzione ai dettagli di basso livello su come funziona l'integrazione di Matplotlib con un ciclo di eventi della GUI. Per un'introduzione più pratica all'API degli eventi Matplotlib, vedere il sistema di gestione degli eventi , il tutorial interattivo e le applicazioni interattive che utilizzano Matplotlib .

Loop di eventi #

Fondamentalmente, tutta l'interazione dell'utente (e il networking) è implementata come un ciclo infinito in attesa di eventi dall'utente (tramite il sistema operativo) e quindi facendo qualcosa al riguardo. Ad esempio, è un REPL (Read Evaluate Print Loop) minimo

exec_count = 0
while True:
    inp = input(f"[{exec_count}] > ")        # Read
    ret = eval(inp)                          # Evaluate
    print(ret)                               # Print
    exec_count += 1                          # Loop

Questo manca di molte sottigliezze (ad esempio, esce alla prima eccezione!), ma è rappresentativo dei cicli di eventi che sono alla base di tutti i terminali, le GUI e i server [ 1 ] . In generale, la fase Read è in attesa di una sorta di I/O, che si tratti di input dell'utente o della rete, mentre Evaluate e Print sono responsabili dell'interpretazione dell'input e quindi dell'azione al riguardo.

In pratica interagiamo con un framework che fornisce un meccanismo per registrare le callback da eseguire in risposta a eventi specifici piuttosto che implementare direttamente il ciclo di I/O [ 2 ] . Ad esempio "quando l'utente fa clic su questo pulsante, eseguire questa funzione" o "quando l'utente preme il tasto 'z', eseguire quest'altra funzione". Ciò consente agli utenti di scrivere programmi reattivi, guidati dagli eventi, senza dover approfondire i dettagli nitidi [ 3 ] dell'I/O. Il ciclo di eventi principale è talvolta indicato come "il ciclo principale" e viene in genere avviato, a seconda della libreria, da metodi con nomi come _exec, runo start.

Tutti i framework GUI (Qt, Wx, Gtk, tk, OSX o web) hanno un metodo per acquisire le interazioni dell'utente e restituirle all'applicazione (ad esempio Signal/ Slotframework in Qt) ma i dettagli esatti dipendono dal toolkit. Matplotlib ha un backend per ogni toolkit GUI che supportiamo che utilizza l'API del toolkit per collegare gli eventi dell'interfaccia utente del toolkit al sistema di gestione degli eventi di Matplotlib . Puoi quindi utilizzare FigureCanvasBase.mpl_connectper connettere la tua funzione al sistema di gestione degli eventi di Matplotlib. Ciò ti consente di interagire direttamente con i tuoi dati e scrivere interfacce utente agnostiche del toolkit GUI.

Integrazione del prompt dei comandi #

Fin qui tutto bene. Abbiamo il REPL (come il terminale IPython) che ci consente di inviare in modo interattivo il codice all'interprete e ottenere i risultati. Abbiamo anche il toolkit della GUI che esegue un ciclo di eventi in attesa dell'input dell'utente e ci consente di registrare le funzioni da eseguire quando ciò accade. Tuttavia, se vogliamo fare entrambe le cose, abbiamo un problema: il prompt e il ciclo di eventi della GUI sono entrambi cicli infiniti che ognuno pensa di essere al comando! Affinché sia ​​il prompt che le finestre della GUI siano reattive, abbiamo bisogno di un metodo per consentire ai loop di "multiproprietà":

  1. lascia che il ciclo principale della GUI blocchi il processo python quando vuoi finestre interattive

  2. lascia che il ciclo principale della CLI blocchi il processo python ed esegua in modo intermittente il ciclo della GUI

  3. incorporare completamente python nella GUI (ma in pratica si tratta di scrivere un'applicazione completa)

Blocco del prompt #

pyplot.show

Visualizza tutte le figure aperte.

pyplot.pause

Esegui il ciclo di eventi della GUI per secondi di intervallo .

backend_bases.FigureCanvasBase.start_event_loop

Avvia un ciclo di eventi di blocco.

backend_bases.FigureCanvasBase.stop_event_loop

Arresta il ciclo di eventi di blocco corrente.

La "integrazione" più semplice consiste nell'avviare il ciclo di eventi della GUI in modalità "blocco" e assumere il controllo della CLI. Mentre il ciclo di eventi della GUI è in esecuzione non puoi inserire nuovi comandi nel prompt (il tuo terminale potrebbe ripetere i caratteri digitati nel terminale, ma non verranno inviati all'interprete Python perché è impegnato a eseguire il ciclo di eventi della GUI), ma le finestre delle figure saranno reattive. Una volta interrotto il ciclo di eventi (lasciando le finestre delle figure ancora aperte non rispondenti) sarà possibile utilizzare nuovamente il prompt. Il riavvio del ciclo di eventi renderà nuovamente reattiva qualsiasi figura aperta (ed elaborerà qualsiasi interazione dell'utente in coda).

Per avviare il ciclo di eventi finché tutte le cifre aperte non sono chiuse, utilizzare pyplot.showas

pyplot.show(block=True)

Per avviare il ciclo di eventi per un periodo di tempo fisso (in secondi) utilizzare pyplot.pause.

Se non lo stai utilizzando pyplot, puoi avviare e interrompere i cicli di eventi tramite FigureCanvasBase.start_event_loope FigureCanvasBase.stop_event_loop. Tuttavia, nella maggior parte dei contesti in cui non verresti utilizzato pyplot, stai incorporando Matplotlib in una grande applicazione GUI e il ciclo di eventi della GUI dovrebbe essere già in esecuzione per l'applicazione.

Lontano dal prompt, questa tecnica può essere molto utile se si desidera scrivere uno script che fa una pausa per l'interazione dell'utente o visualizza una figura tra il polling per ulteriori dati. Vedere Script e funzioni per maggiori dettagli.

Integrazione hook di input #

Mentre l'esecuzione del ciclo di eventi della GUI in modalità di blocco o la gestione esplicita degli eventi dell'interfaccia utente è utile, possiamo fare di meglio! Vogliamo davvero essere in grado di avere un prompt utilizzabile e finestre di figure interattive.

Possiamo farlo usando la funzione 'input hook' del prompt interattivo. Questo hook viene chiamato dal prompt mentre attende che l'utente digiti (anche per un dattilografo veloce il prompt attende principalmente che l'umano pensi e muova le dita). Sebbene i dettagli varino tra i prompt, la logica è approssimativa

  1. iniziare ad attendere l'input da tastiera

  2. avviare il ciclo di eventi della GUI

  3. non appena l'utente preme un tasto, esce dal ciclo di eventi della GUI e gestisce il tasto

  4. ripetere

Questo ci dà l'illusione di avere simultaneamente finestre GUI interattive e un prompt interattivo. La maggior parte delle volte il ciclo di eventi della GUI è in esecuzione, ma non appena l'utente inizia a digitare il prompt riprende il controllo.

Questa tecnica di condivisione del tempo consente solo l'esecuzione del ciclo di eventi mentre Python è altrimenti inattivo e in attesa dell'input dell'utente. Se si desidera che la GUI sia reattiva durante l'esecuzione prolungata del codice, è necessario svuotare periodicamente la coda degli eventi della GUI come descritto sopra . In questo caso è il tuo codice, non il REPL, che sta bloccando il processo, quindi devi gestire manualmente la "multiproprietà". Al contrario, un disegno di figura molto lento bloccherà il prompt fino a quando non finisce di disegnare.

Incorporamento completo #

È anche possibile andare nella direzione opposta e incorporare completamente le figure (e un interprete Python ) in una ricca applicazione nativa. Matplotlib fornisce classi per ogni toolkit che possono essere incorporate direttamente nelle applicazioni GUI (questo è il modo in cui vengono implementate le finestre integrate!). Vedi Incorporamento di Matplotlib nelle interfacce utente grafiche per maggiori dettagli.

Script e funzioni #

backend_bases.FigureCanvasBase.flush_events

Scarica gli eventi della GUI per la figura.

backend_bases.FigureCanvasBase.draw_idle

Richiedi il ridisegno di un widget una volta che il controllo ritorna al ciclo di eventi della GUI.

figure.Figure.ginput

Blocco chiamata per interagire con una figura.

pyplot.ginput

Blocco chiamata per interagire con una figura.

pyplot.show

Visualizza tutte le figure aperte.

pyplot.pause

Esegui il ciclo di eventi della GUI per secondi di intervallo .

Esistono diversi casi d'uso per l'utilizzo di figure interattive negli script:

  • acquisire l'input dell'utente per guidare lo script

  • aggiornamenti di avanzamento con l'avanzamento di uno script a esecuzione prolungata

  • aggiornamenti in streaming da un'origine dati

Funzioni di blocco #

Se hai solo bisogno di raccogliere punti in un Axes puoi usare Figure.ginputo più in generale gli strumenti blocking_inputdegli strumenti si occuperanno di avviare e fermare il ciclo di eventi per te. Tuttavia, se hai scritto o stai utilizzando una gestione degli eventi personalizzata widgets, dovrai eseguire manualmente il ciclo di eventi della GUI utilizzando i metodi descritti sopra .

È inoltre possibile utilizzare i metodi descritti in Blocco della richiesta di sospendere l'esecuzione del ciclo di eventi della GUI. Una volta terminato il ciclo, il codice riprenderà. In generale, qualsiasi posto che useresti time.sleeppuoi usare pyplot.pauseinvece con l'ulteriore vantaggio di figure interattive.

Ad esempio, se si desidera eseguire il polling per i dati, è possibile utilizzare qualcosa di simile

fig, ax = plt.subplots()
ln, = ax.plot([], [])

while True:
    x, y = get_new_data()
    ln.set_data(x, y)
    plt.pause(1)

che eseguirà il polling per nuovi dati e aggiornerà la cifra a 1Hz.

Far girare in modo esplicito l'evento Loop #

backend_bases.FigureCanvasBase.flush_events

Scarica gli eventi della GUI per la figura.

backend_bases.FigureCanvasBase.draw_idle

Richiedi il ridisegno di un widget una volta che il controllo ritorna al ciclo di eventi della GUI.

Se disponi di finestre aperte con eventi dell'interfaccia utente in sospeso (clic del mouse, pressioni di pulsanti o disegni), puoi elaborare esplicitamente tali eventi chiamando FigureCanvasBase.flush_events. Questo eseguirà il ciclo di eventi della GUI fino a quando tutti gli eventi dell'interfaccia utente attualmente in attesa non saranno stati elaborati. Il comportamento esatto dipende dal backend, ma in genere gli eventi su tutte le figure vengono elaborati e verranno gestiti solo gli eventi in attesa di essere elaborati (non quelli aggiunti durante l'elaborazione).

Per esempio

import time
import matplotlib.pyplot as plt
import numpy as np
plt.ion()

fig, ax = plt.subplots()
th = np.linspace(0, 2*np.pi, 512)
ax.set_ylim(-1.5, 1.5)

ln, = ax.plot(th, np.sin(th))

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        ln.figure.canvas.flush_events()

slow_loop(100, ln)

Anche se questo sembrerà un po' lento (poiché elaboriamo l'input dell'utente solo ogni 100 ms mentre 20-30 ms è ciò che sembra "reattivo"), risponderà.

Se apporti modifiche alla trama e desideri che venga ridisegnata, dovrai chiamare draw_idleper richiedere che la tela venga ridisegnata. Questo metodo può essere pensato a draw_soon in analogia a asyncio.loop.call_soon.

Possiamo aggiungere questo al nostro esempio sopra come

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        if j % 10:
            ln.set_ydata(np.sin(((j // 10) % 5 * th)))
            ln.figure.canvas.draw_idle()

        ln.figure.canvas.flush_events()

slow_loop(100, ln)

Più frequentemente chiami, FigureCanvasBase.flush_eventspiù reattiva si sentirà la tua figura, ma a costo di spendere più risorse per la visualizzazione e meno per il tuo calcolo.

Artisti obsoleti #

Gli artisti (a partire da Matplotlib 1.5) hanno un attributo stantioTrue che è se lo stato interno dell'artista è cambiato dall'ultima volta che è stato reso. Per impostazione predefinita, lo stato non aggiornato viene propagato fino ai genitori Artisti nell'albero di disegno, ad esempio, se il colore di Line2D un'istanza viene modificato, anche gli elementi Axese Figureche lo contengono verranno contrassegnati come "stale". Pertanto, fig.stalesegnalerà se qualche artista nella figura è stato modificato e non è sincronizzato con ciò che viene visualizzato sullo schermo. Questo è destinato ad essere utilizzato per determinare se draw_idledeve essere chiamato per programmare un nuovo rendering della figura.

Ogni artista ha un Artist.stale_callbackattributo che contiene un richiamo con la firma

def callback(self: Artist, val: bool) -> None:
   ...

che per impostazione predefinita è impostato su una funzione che inoltra lo stato non aggiornato al genitore dell'artista. Se desideri sopprimere la propagazione di un determinato artista, imposta questo attributo su Nessuno.

Figurele istanze non hanno un artista contenitore e la loro richiamata predefinita è None. Se chiami pyplot.ione non sei IPythonpresente, installeremo un callback da richiamare draw_idleogni volta che Figurediventa obsoleto. In IPythonusiamo l' 'post_execute'hook per invocare draw_idlequalsiasi cifra obsoleta dopo aver eseguito l'input dell'utente, ma prima di restituire il prompt all'utente. Se non lo stai utilizzando pyplot, puoi utilizzare l' Figure.stale_callbackattributo callback per ricevere una notifica quando una figura è diventata obsoleta.

Sorteggio inattivo #

backend_bases.FigureCanvasBase.draw

Rendi il file Figure.

backend_bases.FigureCanvasBase.draw_idle

Richiedi il ridisegno di un widget una volta che il controllo ritorna al ciclo di eventi della GUI.

backend_bases.FigureCanvasBase.flush_events

Scarica gli eventi della GUI per la figura.

In quasi tutti i casi, si consiglia di utilizzare backend_bases.FigureCanvasBase.draw_idleoltre backend_bases.FigureCanvasBase.draw. drawforza un rendering della figura mentre draw_idlepianifica un rendering la prossima volta che la finestra della GUI ridipingerà lo schermo. Ciò migliora le prestazioni eseguendo il rendering solo dei pixel che verranno visualizzati sullo schermo. Se vuoi essere sicuro che lo schermo venga aggiornato il prima possibile, fallo

fig.canvas.draw_idle()
fig.canvas.flush_events()

Filettatura #

La maggior parte dei framework GUI richiede che tutti gli aggiornamenti allo schermo, e quindi il loro ciclo di eventi principale, vengano eseguiti sul thread principale. Ciò rende impossibile inviare aggiornamenti periodici di una trama a un thread in background. Sebbene sembri all'indietro, in genere è più semplice inviare i calcoli a un thread in background e aggiornare periodicamente la figura sul thread principale.

In generale Matplotlib non è thread-safe. Se hai intenzione di aggiornare Artistgli oggetti in un thread e disegnare da un altro, dovresti assicurarti di bloccare le sezioni critiche.

Meccanismo di integrazione Eventloop #

CPython / readline #

L'API Python C fornisce un hook, PyOS_InputHook, per registrare una funzione da eseguire ("La funzione verrà chiamata quando il prompt dell'interprete di Python sta per diventare inattivo e attendere l'input dell'utente dal terminale."). Questo hook può essere utilizzato per integrare un secondo ciclo di eventi (il ciclo di eventi della GUI) con il ciclo del prompt di input di Python. Le funzioni hook in genere esauriscono tutti gli eventi in sospeso sulla coda degli eventi della GUI, eseguono il ciclo principale per un breve periodo di tempo fisso o eseguono il ciclo degli eventi fino a quando non viene premuto un tasto su stdin.

Matplotlib attualmente non esegue alcuna gestione PyOS_InputHooka causa dell'ampia gamma di modi in cui viene utilizzato Matplotlib. Questa gestione è lasciata alle librerie a valle: codice utente o shell. Le figure interattive, anche con Matplotlib in "modalità interattiva", potrebbero non funzionare nel repl di vanilla python se PyOS_InputHooknon è registrato un appropriato.

Gli hook di input e gli helper per installarli sono generalmente inclusi con i collegamenti python per i toolkit GUI e possono essere registrati durante l'importazione. IPython fornisce anche funzioni di hook di input per tutti i framework della GUI supportati da Matplotlib che possono essere installati tramite %matplotlib. Questo è il metodo consigliato per integrare Matplotlib e un prompt.

IPython / prompt_toolkit #

Con IPython >= 5.0 IPython è passato dall'utilizzo del prompt basato su readline di CPython a un prompt_toolkitprompt basato. prompt_toolkit ha lo stesso hook di input concettuale, che viene inserito prompt_toolkittramite il IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook() metodo. La fonte per gli prompt_toolkithook di input risiede in IPython.terminal.pt_inputhooks.

Note a piè di pagina