concurrent.interpreters — Mehrere Interpreter im selben Prozess¶
Hinzugefügt in Version 3.14.
Quellcode: Lib/concurrent/interpreters
Das Modul concurrent.interpreters erstellt Schnittstellen der höheren Ebene auf Basis des Moduls _interpreters auf niedrigerer Ebene.
Das Modul ist hauptsächlich dazu gedacht, eine grundlegende API zur Verwaltung von Interpretern (auch „Sub-Interpretern“ genannt) und zum Ausführen von Code in ihnen bereitzustellen. Das Ausführen beinhaltet hauptsächlich das Umschalten zu einem Interpreter (im aktuellen Thread) und das Aufrufen einer Funktion in diesem Ausführungskontext.
Für Nebenläufigkeit bieten Interpreter selbst (und dieses Modul) nicht viel mehr als Isolation, was allein nicht nützlich ist. Tatsächliche Nebenläufigkeit ist separat über threads verfügbar. Siehe unten.
Siehe auch
InterpreterPoolExecutorkombiniert Threads mit Interpretern in einer vertrauten Schnittstelle.
- Isolierung von Erweiterungsmodulen
wie man ein Erweiterungsmodul für die Unterstützung mehrerer Interpreter aktualisiert
Verfügbarkeit: nicht WASI.
Dieses Modul funktioniert nicht oder ist nicht auf WebAssembly verfügbar. Weitere Informationen finden Sie unter WebAssembly-Plattformen.
Schlüsselleistung¶
Bevor wir tiefer eintauchen, gibt es eine kleine Anzahl von Details zu beachten, wenn mehrere Interpreter verwendet werden:
isoliert, standardmäßig
keine impliziten Threads
nicht alle PyPI-Pakete unterstützen die Verwendung in mehreren Interpretern noch
Einleitung¶
Ein „Interpreter“ ist effektiv der Ausführungskontext der Python-Laufzeitumgebung. Er enthält alle Zustände, die die Laufzeitumgebung zum Ausführen eines Programms benötigt. Dazu gehören Dinge wie der Importzustand und die Builtins. (Jeder Thread, auch wenn es nur der Hauptthread ist, hat zusätzliche Laufzeitstatus, zusätzlich zum aktuellen Interpreter, die sich auf die aktuelle Ausnahme und die Bytecode-Ausführungsschleife beziehen.)
Das Konzept und die Funktionalität des Interpreters sind seit Version 2.2 Teil von Python, aber die Funktion war nur über die C-API verfügbar und nicht gut bekannt, und die Isolation war bis Version 3.12 relativ unvollständig.
Mehrere Interpreter und Isolation¶
Eine Python-Implementierung kann die Verwendung mehrerer Interpreter im selben Prozess unterstützen. CPython hat diese Unterstützung. Jeder Interpreter ist effektiv von den anderen isoliert (mit einer begrenzten Anzahl sorgfältig verwalteter prozessglobaler Ausnahmen von dieser Regel).
Diese Isolation ist hauptsächlich nützlich als starke Trennung zwischen verschiedenen logischen Komponenten eines Programms, bei denen Sie sorgfältig kontrollieren möchten, wie diese Komponenten interagieren.
Hinweis
Interpreter im selben Prozess können technisch niemals streng voneinander isoliert sein, da es nur wenige Einschränkungen für den Speicherzugriff innerhalb desselben Prozesses gibt. Die Python-Laufzeitumgebung bemüht sich nach Kräften um Isolation, aber Erweiterungsmodule können diese leicht verletzen. Verwenden Sie daher keine mehreren Interpreter in sicherheitskritischen Situationen, in denen sie keinen Zugriff auf die Daten der anderen haben sollten.
Ausführen in einem Interpreter¶
Das Ausführen in einem anderen Interpreter beinhaltet das Umschalten zu ihm im aktuellen Thread und dann das Aufrufen einer Funktion. Die Laufzeitumgebung führt die Funktion mit dem Zustand des aktuellen Interpreters aus. Das Modul concurrent.interpreters bietet eine grundlegende API zum Erstellen und Verwalten von Interpretern sowie die Umschalt-und-Aufruf-Operation.
Für den Vorgang werden keine anderen Threads automatisch gestartet. Dafür gibt es jedoch eine Hilfsfunktion. Es gibt eine weitere spezielle Hilfsfunktion zum Aufrufen der integrierten Funktion exec() in einem Interpreter.
Wenn exec() (oder eval()) in einem Interpreter aufgerufen werden, werden sie mit dem Modul __main__ des Interpreters als „globals“-Namensraum ausgeführt. Dasselbe gilt für Funktionen, die keinem Modul zugeordnet sind. Dies ist dasselbe wie die Ausführung von Skripten, die über die Kommandozeile aufgerufen werden, im Modul __main__.
Nebenläufigkeit und Parallelität¶
Wie bereits erwähnt, bieten Interpreter allein keine Nebenläufigkeit. Sie stellen strikt den isolierten Ausführungskontext dar, den die Laufzeitumgebung *im aktuellen Thread* verwendet. Diese Isolation macht sie Prozess-ähnlich, aber sie genießen dennoch In-Prozess-Effizienz, ähnlich wie Threads.
Allerdings unterstützen Interpreter von Natur aus bestimmte Formen der Nebenläufigkeit. Es gibt einen mächtigen Nebeneffekt dieser Isolation. Sie ermöglicht einen anderen Ansatz zur Nebenläufigkeit als bei Async oder Threads. Es ist ein Nebenläufigkeitsmodell, das dem CSP- oder Actor-Modell ähnelt und relativ leicht zu verstehen ist.
Sie können dieses Nebenläufigkeitsmodell in einem einzelnen Thread nutzen und zwischen Interpretern hin- und herschalten, Stackless-Stil. Dieses Modell ist jedoch nützlicher, wenn Sie Interpreter mit mehreren Threads kombinieren. Dies beinhaltet hauptsächlich das Starten eines neuen Threads, in dem Sie zu einem anderen Interpreter wechseln und dort das Gewünschte ausführen.
Jeder tatsächliche Thread in Python, auch wenn Sie nur im Hauptthread laufen, hat seinen eigenen *aktuellen* Ausführungskontext. Mehrere Threads können denselben oder unterschiedliche Interpreter verwenden.
Auf hoher Ebene können Sie die Kombination von Threads und Interpretern als Threads mit optionalem Sharing betrachten.
Als erheblicher Bonus sind Interpreter ausreichend isoliert, dass sie die GIL nicht teilen. Das bedeutet, dass die Kombination von Threads mit mehreren Interpretern volle Multi-Core-Parallelität ermöglicht. (Dies ist seit Python 3.12 der Fall.)
Kommunikation zwischen Interpretern¶
In der Praxis sind mehrere Interpreter nur dann nützlich, wenn wir eine Möglichkeit zur Kommunikation zwischen ihnen haben. Dies beinhaltet normalerweise eine Form der Nachrichtenübermittlung, kann aber auch das Teilen von Daten auf eine sorgfältig verwaltete Weise bedeuten.
Mit diesem Gedanken im Hinterkopf bietet das Modul concurrent.interpreters eine queue.Queue-Implementierung, die über create_queue() verfügbar ist.
Referenz¶
Dieses Modul definiert die folgenden Funktionen
- concurrent.interpreters.list_all()¶
Gibt eine
ListevonInterpreter-Objekten zurück, eines für jeden vorhandenen Interpreter.
- concurrent.interpreters.get_current()¶
Gibt ein
Interpreter-Objekt für den aktuell laufenden Interpreter zurück.
- concurrent.interpreters.get_main()¶
Gibt ein
Interpreter-Objekt für den Hauptinterpreter zurück. Dies ist der Interpreter, den die Laufzeitumgebung zum Ausführen der REPL oder des auf der Kommandozeile angegebenen Skripts erstellt hat. Er ist normalerweise der einzige.
- concurrent.interpreters.create()¶
Initialisiert einen neuen (inaktiven) Python-Interpreter und gibt ein
Interpreter-Objekt dafür zurück.
- concurrent.interpreters.create_queue()¶
Initialisiert eine neue Interpreter-übergreifende Warteschlange und gibt ein
Queue-Objekt dafür zurück.
Interpreter-Objekte¶
- class concurrent.interpreters.Interpreter(id)¶
Ein einzelner Interpreter im aktuellen Prozess.
Im Allgemeinen sollte
Interpreternicht direkt aufgerufen werden. Verwenden Sie stattdessencreate()oder eine der anderen Modulfunktionen.- id¶
(nur lesbar)
Die ID des zugrundeliegenden Interpreters.
- whence¶
(nur lesbar)
Ein String, der beschreibt, woher der Interpreter stammt.
- is_running()¶
Gibt
Truezurück, wenn der Interpreter gerade Code in seinem__main__-Modul ausführt, und andernfallsFalse.
- close()¶
Finalisiert und zerstört den Interpreter.
- prepare_main(ns=None, **kwargs)¶
Bindet Objekte im
__main__-Modul des Interpreters.Einige Objekte werden tatsächlich geteilt und einige werden effizient kopiert, aber die meisten werden über
picklekopiert. Siehe „Sharing“ Objects.
- exec(code, /, dedent=True)¶
Führt den gegebenen Quellcode im Interpreter aus (im aktuellen Thread).
- call(callable, /, *args, **kwargs)¶
Gibt das Ergebnis des Aufrufs der gegebenen Funktion im Interpreter zurück (im aktuellen Thread).
- call_in_thread(callable, /, *args, **kwargs)¶
Führt die gegebene Funktion im Interpreter aus (in einem neuen Thread).
Ausnahmen¶
- exception concurrent.interpreters.InterpreterError¶
Diese Ausnahme, eine Unterklasse von
Exception, wird ausgelöst, wenn ein Interpreter-bezogener Fehler auftritt.
- exception concurrent.interpreters.InterpreterNotFoundError¶
Diese Ausnahme, eine Unterklasse von
InterpreterError, wird ausgelöst, wenn der Zielinterpreter nicht mehr existiert.
- exception concurrent.interpreters.ExecutionFailed¶
Diese Ausnahme, eine Unterklasse von
InterpreterError, wird ausgelöst, wenn der ausgeführte Code eine unbehandelte Ausnahme ausgelöst hat.- excinfo¶
Ein grundlegender Schnappschuss der im anderen Interpreter ausgelösten Ausnahme.
Diese Ausnahme, eine Unterklasse von
TypeError, wird ausgelöst, wenn ein Objekt nicht an einen anderen Interpreter gesendet werden kann.
Kommunikation zwischen Interpretern¶
- class concurrent.interpreters.Queue(id)¶
Ein Wrapper um eine Low-Level-Interpreter-übergreifende Warteschlange, die die Schnittstelle
queue.Queueimplementiert. Die zugrundeliegende Warteschlange kann nur übercreate_queue()erstellt werden.Einige Objekte werden tatsächlich geteilt und einige werden effizient kopiert, aber die meisten werden über
picklekopiert. Siehe „Sharing“ Objects.- id¶
(nur lesbar)
Die ID der Warteschlange.
- exception concurrent.interpreters.QueueEmptyError¶
Diese Ausnahme, eine Unterklasse von
queue.Empty, wird vonQueue.get()undQueue.get_nowait()ausgelöst, wenn die Warteschlange leer ist.
- exception concurrent.interpreters.QueueFullError¶
Diese Ausnahme, eine Unterklasse von
queue.Full, wird vonQueue.put()undQueue.put_nowait()ausgelöst, wenn die Warteschlange voll ist.
Grundlegende Verwendung¶
Erstellen eines Interpreters und Ausführen von Code darin
from concurrent import interpreters
interp = interpreters.create()
# Run in the current OS thread.
interp.exec('print("spam!")')
interp.exec("""if True:
print('spam!')
""")
from textwrap import dedent
interp.exec(dedent("""
print('spam!')
"""))
def run(arg):
return arg
res = interp.call(run, 'spam!')
print(res)
def run():
print('spam!')
interp.call(run)
# Run in new OS thread.
t = interp.call_in_thread(run)
t.join()