Interaktive Figuren und asynchrone Programmierung #
Matplotlib unterstützt reichhaltige interaktive Figuren durch Einbetten von Figuren in ein GUI-Fenster. Die grundlegenden Interaktionen des Schwenkens und Zoomens in einer Achse zum Überprüfen Ihrer Daten sind in Matplotlib „eingebacken“. Dies wird durch ein vollständiges Maus- und Tastatur-Ereignisbehandlungssystem unterstützt, mit dem Sie anspruchsvolle interaktive Diagramme erstellen können.
Dieses Handbuch soll eine Einführung in die grundlegenden Details der Funktionsweise der Matplotlib-Integration mit einer GUI-Ereignisschleife sein. Eine praktischere Einführung in die Matplotlib-Ereignis-API finden Sie unter Ereignisbehandlungssystem , Interaktives Tutorial und Interaktive Anwendungen mit Matplotlib .
Ereignisschleifen #
Grundsätzlich wird jede Benutzerinteraktion (und Vernetzung) als Endlosschleife implementiert, die auf Ereignisse vom Benutzer (über das Betriebssystem) wartet und dann etwas dagegen unternimmt. Beispielsweise ist eine minimale Read Evaluate Print Loop (REPL) möglich
exec_count = 0
while True:
inp = input(f"[{exec_count}] > ") # Read
ret = eval(inp) # Evaluate
print(ret) # Print
exec_count += 1 # Loop
Diesem fehlen viele Feinheiten (z. B. wird es bei der ersten Ausnahme beendet!), aber es ist repräsentativ für die Ereignisschleifen, die allen Terminals, GUIs und Servern zugrunde liegen [ 1 ] . Im Allgemeinen wartet der Read -Schritt auf eine Art I/O – sei es Benutzereingabe oder das Netzwerk – während Evaluate und Print dafür verantwortlich sind, die Eingabe zu interpretieren und dann etwas dagegen zu unternehmen.
In der Praxis interagieren wir mit einem Framework, das einen Mechanismus bereitstellt, um Callbacks zu registrieren, die als Reaktion auf bestimmte Ereignisse ausgeführt werden sollen, anstatt die I/O-Schleife direkt zu implementieren [ 2 ] . Zum Beispiel „wenn der Benutzer auf diese Schaltfläche klickt, führen Sie bitte diese Funktion aus“ oder „wenn der Benutzer die Taste „z“ drückt, führen Sie bitte diese andere Funktion aus“. Dies ermöglicht es Benutzern, reaktive, ereignisgesteuerte Programme zu schreiben, ohne sich in die wesentlichen [ 3 ] Details von I/O vertiefen zu müssen. Die zentrale Ereignisschleife wird manchmal als "Hauptschleife" bezeichnet und wird je nach Bibliothek normalerweise von Methoden mit Namen wie _exec
,
run
oder gestartet start
.
Alle GUI-Frameworks (Qt, Wx, Gtk, tk, OSX oder Web) verfügen über eine Methode zum Erfassen von Benutzerinteraktionen und deren Rückgabe an die Anwendung (z. B. Signal
/ Slot
framework in Qt), aber die genauen Details hängen vom Toolkit ab. Matplotlib hat ein Backend für jedes von uns unterstützte GUI-Toolkit, das die Toolkit-API verwendet, um die Toolkit-UI-Ereignisse in das Ereignisbehandlungssystem von Matplotlib zu überbrücken . Sie können dann verwenden
FigureCanvasBase.mpl_connect
, um Ihre Funktion mit dem Ereignisbehandlungssystem von Matplotlib zu verbinden. Auf diese Weise können Sie direkt mit Ihren Daten interagieren und GUI-Toolkit-agnostische Benutzeroberflächen schreiben.
Eingabeaufforderungsintegration #
So weit, ist es gut. Wir haben die REPL (wie das IPython-Terminal), mit der wir interaktiv Code an den Interpreter senden und Ergebnisse zurückerhalten können. Wir haben auch das GUI-Toolkit, das eine Ereignisschleife ausführt, die auf Benutzereingaben wartet, und uns Funktionen registrieren lässt, die ausgeführt werden sollen, wenn dies geschieht. Wenn wir jedoch beides tun wollen, haben wir ein Problem: Die Eingabeaufforderung und die GUI-Ereignisschleife sind beide Endlosschleifen, von denen jeder glaubt, dass sie verantwortlich sind! Damit sowohl die Eingabeaufforderung als auch die GUI-Fenster reagieren, benötigen wir eine Methode, mit der die Schleifen "timeshare" können:
Lassen Sie die GUI-Hauptschleife den Python-Prozess blockieren, wenn Sie interaktive Fenster wünschen
Lassen Sie die CLI-Hauptschleife den Python-Prozess blockieren und die GUI-Schleife zeitweise ausführen
Python vollständig in die GUI einbetten (aber dies ist im Grunde das Schreiben einer vollständigen Anwendung)
Blockieren der Eingabeaufforderung #
Alle offenen Zahlen anzeigen. |
|
Führen Sie die GUI-Ereignisschleife für Intervallsekunden aus. |
|
Starten Sie eine blockierende Ereignisschleife. |
|
Beenden Sie die aktuelle Blockierungsereignisschleife. |
Die einfachste "Integration" besteht darin, die GUI-Ereignisschleife im Modus "Blockieren" zu starten und die CLI zu übernehmen. Während die GUI-Ereignisschleife ausgeführt wird, können Sie keine neuen Befehle in die Eingabeaufforderung eingeben (Ihr Terminal gibt möglicherweise die in das Terminal eingegebenen Zeichen wieder, aber sie werden nicht an den Python-Interpreter gesendet, da er damit beschäftigt ist, die GUI-Ereignisschleife auszuführen), aber die Abbildungsfenster reagieren. Sobald die Ereignisschleife beendet ist (wobei alle noch geöffneten Abbildungsfenster nicht mehr reagieren), können Sie die Eingabeaufforderung wieder verwenden. Durch das erneute Starten der Ereignisschleife reagiert jede offene Figur wieder (und verarbeitet alle Benutzerinteraktionen in der Warteschlange).
Um die Ereignisschleife zu starten, bis alle offenen Figuren geschlossen sind, verwenden Sie
pyplot.show
as
pyplot.show(block=True)
Um die Ereignisschleife für eine festgelegte Zeitspanne (in Sekunden) zu starten, verwenden Sie
pyplot.pause
.
Wenn Sie es nicht verwenden pyplot
, können Sie die Ereignisschleifen über FigureCanvasBase.start_event_loop
und
starten und stoppen FigureCanvasBase.stop_event_loop
. In den meisten Kontexten, in denen Sie Matplotlib jedoch nicht verwenden würden, betten pyplot
Sie Matplotlib in eine große GUI-Anwendung ein, und die GUI-Ereignisschleife sollte bereits für die Anwendung ausgeführt werden.
Abgesehen von der Eingabeaufforderung kann diese Technik sehr nützlich sein, wenn Sie ein Skript schreiben möchten, das für eine Benutzerinteraktion pausiert oder eine Zahl zwischen dem Abrufen zusätzlicher Daten anzeigt. Weitere Einzelheiten finden Sie unter Skripts und Funktionen .
Input-Hook-Integration #
Während das Ausführen der GUI-Ereignisschleife in einem Blockiermodus oder das explizite Behandeln von UI-Ereignissen nützlich ist, können wir es besser machen! Wir möchten wirklich in der Lage sein, eine brauchbare Eingabeaufforderung und interaktive Figurenfenster zu haben.
Wir können dies mit der Funktion „Eingabehaken“ der interaktiven Eingabeaufforderung tun. Dieser Hook wird von der Eingabeaufforderung aufgerufen, während sie darauf wartet, dass der Benutzer tippt (selbst für eine schnelle Schreibkraft wartet die Eingabeaufforderung meistens darauf, dass der Mensch nachdenkt und seine Finger bewegt). Obwohl die Details zwischen den Eingabeaufforderungen variieren, ist die Logik grob
Beginnen Sie, auf Tastatureingaben zu warten
Starten Sie die GUI-Ereignisschleife
Sobald der Benutzer eine Taste drückt, verlassen Sie die GUI-Ereignisschleife und behandeln Sie die Taste
wiederholen
Dies gibt uns die Illusion, gleichzeitig interaktive GUI-Fenster und eine interaktive Eingabeaufforderung zu haben. Die meiste Zeit läuft die GUI-Ereignisschleife, aber sobald der Benutzer mit der Eingabe beginnt, übernimmt die Eingabeaufforderung wieder.
Diese Timesharing-Technik lässt die Ausführung der Ereignisschleife nur zu, während Python ansonsten im Leerlauf ist und auf Benutzereingaben wartet. Wenn Sie möchten , dass die GUI während langer Codeausführung reagiert , ist es notwendig , die GUI - Ereigniswarteschlange wie oben beschrieben regelmäßig zu leeren . In diesem Fall ist es Ihr Code, nicht die REPL, die den Prozess blockiert, sodass Sie den "Time-Share" manuell handhaben müssen. Umgekehrt blockiert ein sehr langsames Zeichnen einer Figur die Eingabeaufforderung, bis das Zeichnen abgeschlossen ist.
Vollständige Einbettung #
Es ist auch möglich, in die andere Richtung zu gehen und Figuren (und einen Python-Interpreter ) vollständig in eine reichhaltige native Anwendung einzubetten. Matplotlib bietet Klassen für jedes Toolkit, die direkt in GUI-Anwendungen eingebettet werden können (so werden die eingebauten Fenster implementiert!). Weitere Einzelheiten finden Sie unter Einbetten von Matplotlib in grafische Benutzeroberflächen .
Skripte und Funktionen #
Leeren Sie die GUI-Ereignisse für die Figur. |
|
Fordern Sie eine Widget-Neuzeichnung an, sobald die Steuerung zur GUI-Ereignisschleife zurückkehrt. |
|
Anruf blockieren, um mit einer Figur zu interagieren. |
|
Anruf blockieren, um mit einer Figur zu interagieren. |
|
Alle offenen Zahlen anzeigen. |
|
Führen Sie die GUI-Ereignisschleife für Intervallsekunden aus. |
Es gibt mehrere Anwendungsfälle für die Verwendung interaktiver Figuren in Skripten:
Erfassen Sie Benutzereingaben, um das Skript zu steuern
Fortschrittsaktualisierungen, wenn ein lange laufendes Skript fortschreitet
Streaming-Updates von einer Datenquelle
Sperrfunktionen #
Wenn Sie nur Punkte in einer Achse sammeln müssen, können Sie
Figure.ginput
die Tools von
blocking_input
Tools verwenden, um das Starten und Stoppen der Ereignisschleife für Sie zu erledigen. Wenn Sie jedoch eine benutzerdefinierte Ereignisbehandlung geschrieben haben oder verwenden widgets
, müssen Sie die GUI-Ereignisschleife mit den oben beschriebenen Methoden manuell ausführen .
Sie können auch die unter Blockieren der Eingabeaufforderung beschriebenen Methoden verwenden
, um die Ausführung der GUI-Ereignisschleife auszusetzen. Sobald die Schleife beendet wird, wird Ihr Code fortgesetzt. Im Allgemeinen können Sie stattdessen jeden Ort verwenden, den Sie time.sleep
verwenden
würden, pyplot.pause
mit dem zusätzlichen Vorteil interaktiver Figuren.
Wenn Sie beispielsweise Daten abfragen möchten, können Sie so etwas wie verwenden
fig, ax = plt.subplots()
ln, = ax.plot([], [])
while True:
x, y = get_new_data()
ln.set_data(x, y)
plt.pause(1)
die nach neuen Daten abfragen und die Zahl bei 1 Hz aktualisieren würde.
Explizites Drehen des Events Loop #
Leeren Sie die GUI-Ereignisse für die Figur. |
|
Fordern Sie eine Widget-Neuzeichnung an, sobald die Steuerung zur GUI-Ereignisschleife zurückkehrt. |
Wenn Sie offene Fenster mit ausstehenden UI-Ereignissen (Mausklicks, Tastendruck oder Ziehen) haben, können Sie diese Ereignisse explizit verarbeiten, indem Sie aufrufen FigureCanvasBase.flush_events
. Dadurch wird die GUI-Ereignisschleife ausgeführt, bis alle derzeit wartenden UI-Ereignisse verarbeitet wurden. Das genaue Verhalten ist vom Backend abhängig, aber normalerweise werden Ereignisse auf allen Zahlen verarbeitet und nur Ereignisse, die darauf warten, verarbeitet zu werden (nicht die während der Verarbeitung hinzugefügten), werden behandelt.
Zum Beispiel
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)
Dies wird sich zwar etwas träge anfühlen (da wir Benutzereingaben nur alle 100 ms verarbeiten, während sich 20-30 ms "reaktionsschnell" anfühlen), reagiert es jedoch.
Wenn Sie Änderungen an der Handlung vornehmen und diese neu gerendert haben möchten, müssen Sie anrufen draw_idle
, um anzufordern, dass die Leinwand neu gezeichnet wird. Diese Methode kann man sich in Analogie zu
draw_soon vorstellenasyncio.loop.call_soon
.
Wir können dies zu unserem obigen Beispiel hinzufügen als
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)
Je häufiger Sie anrufen, FigureCanvasBase.flush_events
desto reaktionsschneller wird sich Ihre Figur anfühlen, aber auf Kosten von mehr Ressourcen für die Visualisierung und weniger für Ihre Berechnung.
Veraltete Künstler #
Künstler (ab Matplotlib 1.5) haben ein veraltetes Attribut, das heißt,
True
wenn sich der interne Zustand des Künstlers seit dem letzten Mal geändert hat, als er gerendert wurde. Standardmäßig wird der veraltete Zustand bis zu den übergeordneten Künstlern im Zeichnungsbaum weitergegeben, zB wenn die Farbe einer Line2D
Instanz geändert wird, werden die Axes
und Figure
die sie enthalten ebenfalls als "veraltet" markiert. Daher fig.stale
wird gemeldet, wenn ein Künstler in der Figur geändert wurde und nicht mehr mit dem übereinstimmt, was auf dem Bildschirm angezeigt wird. Dies soll verwendet werden, um zu bestimmen, ob draw_idle
aufgerufen werden sollte, um ein erneutes Rendern der Figur zu planen.
Jeder Künstler hat ein Artist.stale_callback
Attribut, das einen Rückruf mit der Signatur enthält
def callback(self: Artist, val: bool) -> None:
...
die standardmäßig auf eine Funktion eingestellt ist, die den veralteten Zustand an das übergeordnete Element des Künstlers weiterleitet. Wenn Sie die Verbreitung eines bestimmten Künstlers unterdrücken möchten, setzen Sie dieses Attribut auf „Keine“.
Figure
Instanzen haben keinen enthaltenden Künstler und ihr Standardrückruf ist None
. Wenn Sie anrufen pyplot.ion
und nicht dabei sind
IPython
, installieren wir einen Rückruf,
der aufgerufen wird, draw_idle
wenn der
Figure
veraltet wird. In IPython
verwenden wir den
'post_execute'
Hook, um
draw_idle
veraltete Zahlen aufzurufen, nachdem wir die Eingabe des Benutzers ausgeführt haben, aber bevor wir die Eingabeaufforderung an den Benutzer zurückgeben. Wenn Sie es nicht verwenden pyplot
, können Sie das Callback
Figure.stale_callback
-Attribut verwenden, um benachrichtigt zu werden, wenn eine Figur veraltet ist.
Leerlauf ziehen #
Rendern Sie die |
|
Fordern Sie eine Widget-Neuzeichnung an, sobald die Steuerung zur GUI-Ereignisschleife zurückkehrt. |
|
Leeren Sie die GUI-Ereignisse für die Figur. |
In fast allen Fällen empfehlen wir die Verwendung von
backend_bases.FigureCanvasBase.draw_idle
über
backend_bases.FigureCanvasBase.draw
. draw
erzwingt ein Rendern der Figur, während draw_idle
ein Rendern geplant wird, wenn das GUI-Fenster das nächste Mal den Bildschirm neu zeichnet. Dies verbessert die Leistung, indem nur Pixel gerendert werden, die auf dem Bildschirm angezeigt werden. Wenn Sie sicher sein möchten, dass der Bildschirm so schnell wie möglich aktualisiert wird, tun Sie dies
fig.canvas.draw_idle()
fig.canvas.flush_events()
Einfädeln #
Die meisten GUI-Frameworks erfordern, dass alle Aktualisierungen des Bildschirms und damit ihre Hauptereignisschleife im Hauptthread ausgeführt werden. Dies macht es unmöglich, regelmäßige Aktualisierungen eines Diagramms an einen Hintergrundthread zu senden. Obwohl es rückwärts scheint, ist es in der Regel einfacher, Ihre Berechnungen in einen Hintergrundthread zu verschieben und die Abbildung regelmäßig im Hauptthread zu aktualisieren.
Im Allgemeinen ist Matplotlib nicht Thread-sicher. Wenn Sie Objekte in einem Thread aktualisieren
Artist
und aus einem anderen ziehen, sollten Sie sicherstellen, dass Sie die kritischen Abschnitte sperren.
Eventloop-Integrationsmechanismus #
CPython / readline #
Die Python-C-API bietet einen Hook, PyOS_InputHook
, um eine auszuführende Funktion zu registrieren ("Die Funktion wird aufgerufen, wenn Pythons Interpreter-Eingabeaufforderung im Begriff ist, inaktiv zu werden und auf Benutzereingaben vom Terminal zu warten."). Dieser Hook kann verwendet werden, um eine zweite Ereignisschleife (die GUI-Ereignisschleife) in die Python-Eingabeaufforderungsschleife zu integrieren. Die Hook-Funktionen erschöpfen normalerweise alle anstehenden Ereignisse in der GUI-Ereigniswarteschlange, führen die Hauptschleife für eine kurze festgelegte Zeitspanne aus oder führen die Ereignisschleife aus, bis eine Taste auf stdin gedrückt wird.
Matplotlib führt derzeit keine Verwaltung PyOS_InputHook
durch, da Matplotlib auf vielfältige Weise verwendet wird. Diese Verwaltung wird nachgelagerten Bibliotheken überlassen – entweder dem Benutzercode oder der Shell. Interaktive Figuren, selbst mit Matplotlib im „interaktiven Modus“, funktionieren möglicherweise nicht in der Vanilla-Python-Repl, wenn keine entsprechende PyOS_InputHook
registriert ist.
Eingabe-Hooks und Helfer zu ihrer Installation sind normalerweise in den Python-Bindungen für GUI-Toolkits enthalten und können beim Import registriert werden. IPython liefert auch Eingabe-Hook-Funktionen für alle von Matplotlib unterstützten GUI-Frameworks, die über installiert werden können %matplotlib
. Dies ist die empfohlene Methode zur Integration von Matplotlib und einer Eingabeaufforderung.
IPython / prompt_toolkit #
Mit IPython >= 5.0 hat sich IPython von der Verwendung der Readline-basierten Eingabeaufforderung von CPython zu einer prompt_toolkit
basierten Eingabeaufforderung geändert. prompt_toolkit
hat denselben konzeptionellen Input-Hook, in prompt_toolkit
den über die
IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook()
Methode eingespeist wird. Die Quelle für die prompt_toolkit
Input-Hooks befindet sich unter
IPython.terminal.pt_inputhooks
.
Fußnoten