4. Ausführungsmodell¶
4.1. Aufbau eines Programms¶
Ein Python-Programm setzt sich aus Codeblöcken zusammen. Ein Block ist ein Teil des Python-Programmtextes, der als Einheit ausgeführt wird. Zu den Blöcken gehören: ein Modul, ein Funktionskörper und eine Klassendefinition. Jeder interaktiv eingegebene Befehl ist ein Block. Eine Skriptdatei (eine Datei, die dem Interpreter als Standardeingabe übergeben oder als Befehlszeilenargument für den Interpreter angegeben wird) ist ein Codeblock. Ein Skriptbefehl (ein Befehl, der in der Befehlszeile des Interpreters mit der Option -c angegeben wird) ist ein Codeblock. Ein Modul, das als Skript der obersten Ebene (als Modul __main__ ) über die Befehlszeile mit dem Argument -m ausgeführt wird, ist ebenfalls ein Codeblock. Das an die integrierten Funktionen eval() und exec() übergebene Zeichenfolgenargument ist ein Codeblock.
Ein Codeblock wird in einem Ausführungsrahmen ausgeführt. Ein Rahmen enthält einige Verwaltungsinformationen (die zur Fehlersuche dienen) und legt fest, wo und wie die Ausführung fortgesetzt wird, nachdem die Ausführung des Codeblocks abgeschlossen ist.
4.2. Benennung und Zuordnung¶
4.2.1. Namensbindung¶
Namen beziehen sich auf Objekte. Namen werden durch Namensbindungsoperationen eingeführt.
Die folgenden Konstrukte binden Namen:
Formale Parameter von Funktionen,
Klassendefinitionen,
Funktionsdefinitionen,
Zuweisungsausdrücke,
Ziele, die als Bezeichner gelten, wenn sie in einer Zuweisung vorkommen:
importAussagen.typeAussagen.
Die Anweisung import in der Form from ... import * bindet alle im importierten Modul definierten Namen, mit Ausnahme derjenigen, die mit einem Unterstrich beginnen. Diese Form darf nur auf Modulebene verwendet werden.
Ein Ziel, das in einer Anweisung vom Typ del vorkommt, gilt für diesen Zweck ebenfalls als gebunden (auch wenn die eigentliche Semantik darin besteht, die Bindung des Namens aufzuheben).
Jede Zuweisungs- oder Importanweisung erfolgt innerhalb eines Blocks, der durch eine Klassen- oder Funktionsdefinition definiert ist, oder auf Modulebene (dem Code-Block der obersten Ebene).
Wird ein Name in einem Block gebunden, handelt es sich um eine lokale Variable dieses Blocks, sofern er nicht als nonlocal oder global deklariert wurde. Wird ein Name auf Modulebene gebunden, handelt es sich um eine globale Variable. (Die Variablen des Modul-Codeblocks sind sowohl lokal als auch global.) Wird eine Variable in einem Codeblock verwendet, dort aber nicht definiert, handelt es sich um eine freie Variable.
Jedes Vorkommen eines Namens im Programmtext bezieht sich auf die Bindung dieses Namens, die durch die folgenden Namensauflösungsregeln festgelegt wird.
4.2.2. Namensauflösung¶
Ein Gültigkeitsbereich legt fest, in welchem Umfang ein Name innerhalb eines Blocks sichtbar ist. Wird eine lokale Variable in einem Block definiert, erstreckt sich ihr Gültigkeitsbereich auf diesen Block. Erfolgt die Definition in einem Funktionsblock, erstreckt sich der Gültigkeitsbereich auf alle Blöcke, die in dem definierenden Block enthalten sind, es sei denn, ein darin enthaltener Block führt eine andere Bindung für den Namen ein.
Wenn ein Name in einem Codeblock verwendet wird, wird er anhand des nächstgelegenen übergeordneten Gültigkeitsbereichs aufgelöst. Die Menge aller solcher Gültigkeitsbereiche, die für einen Codeblock sichtbar sind, wird als Umgebung des Blocks bezeichnet.
Wenn ein Name überhaupt nicht gefunden wird, wird eine Ausnahme vom Typ NameError ausgelöst. Handelt es sich beim aktuellen Gültigkeitsbereich um einen Funktionsbereich und bezieht sich der Name auf eine lokale Variable, die an der Stelle, an der der Name verwendet wird, noch nicht mit einem Wert belegt wurde, wird eine Ausnahme vom Typ UnboundLocalError ausgelöst. UnboundLocalError ist eine Unterklasse von NameError .
Wenn irgendwo innerhalb eines Codeblocks eine Namensbindung stattfindet, werden alle Verwendungen des Namens innerhalb des Blocks als Verweise auf den aktuellen Block behandelt. Dies kann zu Fehlern führen, wenn ein Name innerhalb eines Blocks verwendet wird, bevor er gebunden wurde. Diese Regel ist nicht ganz einfach zu verstehen. Python kennt keine Deklarationen und erlaubt es, dass Namensbindungsoperationen an beliebiger Stelle innerhalb eines Codeblocks stattfinden. Die lokalen Variablen eines Codeblocks lassen sich ermitteln, indem der gesamte Text des Blocks auf Namensbindungsoperationen durchsucht wird. Beispiele finden Sie im FAQ-Eintrag zu UnboundLocalError.
Wenn die Anweisung global innerhalb eines Blocks auftritt, beziehen sich alle Verwendungen der in der Anweisung angegebenen Namen auf die Zuordnungen dieser Namen im obersten Namensraum. Namen werden im obersten Namensraum aufgelöst, indem der globale Namensraum, d. h. der Namensraum des Moduls, das den Codeblock enthält, sowie der „builtins“-Namensraum, der Namensraum des Moduls builtins, durchsucht werden. Der globale Namensraum wird zuerst durchsucht. Werden die Namen dort nicht gefunden, wird als Nächstes der „builtins“-Namensraum durchsucht. Werden die Namen auch im „builtins“-Namensraum nicht gefunden, werden neue Variablen im globalen Namensraum angelegt. Die Anweisung „global“ muss allen Verwendungen der aufgeführten Namen vorangestellt werden.
Die Anweisung global hat denselben Geltungsbereich wie eine Namensbindung im selben Block. Wenn der nächstübergeordnete Geltungsbereich einer freien Variablen eine globale Anweisung enthält, wird die freie Variable als globale Variable behandelt.
Die Anweisung nonlocal bewirkt, dass die entsprechenden Namen auf zuvor gebundene Variablen im nächstübergeordneten Funktionsgültigkeitsbereich verweisen. Wenn der angegebene Name in keinem übergeordneten Funktionsgültigkeitsbereich existiert, wird zur Kompilierungszeit ein SyntaxError ausgelöst. Typ-Parameter können mit der Anweisung nonlocal nicht neu gebunden werden.
Der Namensraum für ein Modul wird automatisch angelegt, sobald das Modul zum ersten Mal importiert wird. Das Hauptmodul eines Skripts heißt immer __main__ .
Klassendefinitionsblöcke und Argumente für exec() und eval() stellen im Zusammenhang mit der Namensauflösung einen Sonderfall dar. Eine Klassendefinition ist eine ausführbare Anweisung, die Namen verwenden und definieren kann. Diese Verweise folgen den üblichen Regeln für die Namensauflösung, mit der Ausnahme, dass ungebundene lokale Variablen im globalen Namensraum gesucht werden. Der Namensraum der Klassendefinition wird zum Attributwörterbuch der Klasse. Der Geltungsbereich von Namen, die in einem Klassenblock definiert werden, ist auf den Klassenblock beschränkt; er erstreckt sich nicht auf die Codeblöcke von Methoden. Dies umfasst Comprehensions und Generatorausdrücke, jedoch nicht Annotations-Geltungsbereiche, die Zugriff auf die Geltungsbereiche ihrer umschließenden Klassen haben. Das bedeutet, dass Folgendes fehlschlagen wird:
Klasse A:
a = 42
b = list(a + i for i in range(10))
Folgendes wird jedoch funktionieren:
Klasse A:
Typ Alias = Nested
Klasse Nested: pass
print(A.Alias.__value__) # <type 'A.Nested'>
4.2.3. Gültigkeitsbereiche von Annotationen¶
Type parameter lists and type statements
introduce annotation scopes, which behave mostly like function scopes,
but with some exceptions discussed below. Annotations
currently do not use annotation scopes, but they are expected to use
annotation scopes in Python 3.13 when PEP 649 is implemented.
Annotationsbereiche werden in den folgenden Kontexten verwendet:
Typ-Parameterlisten für generische Typ-Aliase.
Typ-Parameterlisten für generische Funktionen. Die Annotationen einer generischen Funktion werden innerhalb des Annotationsbereichs ausgeführt, ihre Standardwerte und Dekoratoren hingegen nicht.
Typ-Parameterlisten für generische Klassen. Die Basisklassen und Schlüsselwortargumente einer generischen Klasse werden innerhalb des Annotationsbereichs ausgeführt, ihre Dekoratoren jedoch nicht.
Die Grenzen, Einschränkungen und Standardwerte für Typparameter (verzögert ausgewertete).
Der Wert von Typaliasen (:ref:` <lazy-evaluation>`, die verzögert ausgewertet werden).
Annotations-Gültigkeitsbereiche unterscheiden sich in folgenden Punkten von Funktions-Gültigkeitsbereichen:
Annotationsbereiche haben Zugriff auf den Namensraum ihrer übergeordneten Klasse. Befindet sich ein Annotationsbereich unmittelbar innerhalb eines Klassenbereichs oder innerhalb eines anderen Annotationsbereichs, der sich wiederum unmittelbar innerhalb eines Klassenbereichs befindet, kann der Code im Annotationsbereich auf im Klassenbereich definierte Namen zugreifen, als würde er direkt innerhalb des Klassenkörpers ausgeführt. Dies steht im Gegensatz zu regulären, innerhalb von Klassen definierten Funktionen, die keinen Zugriff auf im Klassenbereich definierte Namen haben.
Ausdrücke in Annotationsbereichen dürfen keine Ausdrücke vom Typ
yield,yield from,awaitoder:=enthalten. (Diese Ausdrücke sind in anderen Bereichen zulässig, die innerhalb des Annotationsbereichs enthalten sind.)Namen, die in Annotationsbereichen definiert wurden, können nicht mit
nonlocal-Anweisungen in inneren Bereichen neu definiert werden. Dies betrifft ausschließlich Typparameter, da keine anderen syntaktischen Elemente, die innerhalb von Annotationsbereichen vorkommen können, neue Namen einführen können.Annotationsbereiche verfügen zwar über einen internen Namen, dieser Name spiegelt sich jedoch nicht im qualifizierten Namen der innerhalb des Bereichs definierten Objekte wider. Stattdessen entspricht der
__qualname__solcher Objekte dem, als wäre das Objekt im übergeordneten Bereich definiert.
Added in version 3.12: Annotationsbereiche wurden in Python 3.12 als Teil von PEP 695 eingeführt.
Geändert in Version 3.13: Annotationsbereiche werden auch für Standardwerte von Typparametern verwendet, wie unter PEP 696 beschrieben.
4.2.4. Verzögerte Auswertung¶
The values of type aliases created through the type statement are
lazily evaluated. The same applies to the bounds, constraints, and default values of type
variables created through the type parameter syntax.
This means that they are not evaluated when the type alias or type variable is
created. Instead, they are only evaluated when doing so is necessary to resolve
an attribute access.
Beispiel:
>>> type Alias = 1/0
>>> Alias.__value__
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> def func[T: 1/0](): pass
>>> T = func.__type_params__[0]
>>> T.__bound__
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
Hier wird die Ausnahme nur ausgelöst, wenn auf das Attribut __value__ des Typalias oder auf das Attribut __bound__ der Typvariablen zugegriffen wird.
Dieses Verhalten ist vor allem bei Verweisen auf Typen nützlich, die zum Zeitpunkt der Erstellung des Typalias oder der Typvariablen noch nicht definiert waren. So ermöglicht die verzögerte Auswertung beispielsweise die Erstellung von gegenseitig rekursiven Typalias:
from typing import Literal
type SimpleExpr = int | Parenthesized
type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]
Verzögerte ausgewertete Werte werden im Annotationsbereich ausgewertet, was bedeutet, dass Namen, die innerhalb des verzögertausgewerteten Werts vorkommen, so nachgeschlagen werden, als würden sie im unmittelbar umgebenden Bereich verwendet.
Added in version 3.12.
4.2.5. Eingebaute Funktionen und eingeschränkte Ausführung¶
Benutzer sollten __builtins__ nicht ändern; es handelt sich dabei ausschließlich um ein Implementierungsdetail. Benutzer, die Werte im Namespace „builtins“ überschreiben möchten, sollten das Modul builtins über import laden und dessen Attribute entsprechend anpassen.
Der mit der Ausführung eines Codeblocks verbundene „builtins“-Namensraum wird tatsächlich durch Abfrage des Namens __builtins__ in dessen globalem Namensraum ermittelt; dabei sollte es sich um ein Wörterbuch oder ein Modul handeln (im letzteren Fall wird das Wörterbuch des Moduls verwendet). Standardmäßig ist __builtins__ im Modul __main__ das eingebaute Modul builtins ; in jedem anderen Modul ist __builtins__ ein Alias für das Wörterbuch des Moduls builtins selbst.
4.2.6. Interaktion mit dynamischen Funktionen¶
Die Auflösung freier Variablen erfolgt zur Laufzeit und nicht zur Kompilierungszeit. Das bedeutet, dass der folgende Code 42 ausgibt:
i = 10
def f():
print(i)
i = 42
f()
Die Funktionen eval() und exec() haben keinen Zugriff auf die vollständige Umgebung zur Namensauflösung. Namen können im lokalen und globalen Namensraum des Aufrufers aufgelöst werden. Freie Variablen werden nicht im nächstübergeordneten Namensraum, sondern im globalen Namensraum aufgelöst. [1] Die Funktionen exec() und eval() verfügen über optionale Argumente, um den globalen und lokalen Namensraum zu überschreiben. Wird nur ein Namensraum angegeben, wird dieser für beide verwendet.
4.3. Ausnahmen¶
Ausnahmen dienen dazu, den normalen Kontrollfluss eines Codeblocks zu unterbrechen, um Fehler oder andere Ausnahmebedingungen zu behandeln. Eine Ausnahme wird an der Stelle ausgelöst, an der der Fehler erkannt wird; sie kann vom umgebenden Codeblock oder von jedem Codeblock behandelt werden, der den Codeblock, in dem der Fehler aufgetreten ist, direkt oder indirekt aufgerufen hat.
Der Python-Interpreter löst eine Ausnahme aus, wenn er einen Laufzeitfehler (wie beispielsweise eine Division durch Null) feststellt. Ein Python-Programm kann zudem mit der Anweisung raise explizit eine Ausnahme auslösen. Ausnahmebehandler werden mit der Anweisung try… except festgelegt. Die Klausel finally einer solchen Anweisung kann verwendet werden, um Aufräumcode anzugeben, der die Ausnahme nicht behandelt, sondern unabhängig davon ausgeführt wird, ob im vorangehenden Code eine Ausnahme aufgetreten ist oder nicht.
Python verwendet das „Termination“-Modell der Fehlerbehandlung: Ein Ausnahmebehandler kann zwar feststellen, was passiert ist, und die Ausführung auf einer übergeordneten Ebene fortsetzen, aber er kann die Fehlerursache nicht beheben und den fehlgeschlagenen Vorgang nicht wiederholen (außer durch erneutes Ausführen des fehlerhaften Codeabschnitts von Anfang an).
Wird eine Ausnahme überhaupt nicht abgefangen, bricht der Interpreter die Ausführung des Programms ab oder kehrt zu seiner interaktiven Hauptschleife zurück. In beiden Fällen gibt er einen Stack-Traceback aus, es sei denn, es handelt sich um die Ausnahme SystemExit .
Ausnahmen werden anhand von Klasseninstanzen identifiziert. Die Klausel except wird je nach Klasse der Instanz ausgewählt: Sie muss auf die Klasse der Instanz oder auf eine nicht-virtuelle Basisklasse derselben verweisen. Die Instanz kann vom Handler empfangen werden und zusätzliche Informationen über die Ausnahmebedingung enthalten.
Bemerkung
Ausnahmemeldungen sind nicht Teil der Python-API. Ihr Inhalt kann sich von einer Python-Version zur nächsten ohne Vorwarnung ändern, weshalb sich Code, der unter mehreren Versionen des Interpreters ausgeführt wird, nicht darauf verlassen sollte.
Siehe auch die Beschreibung der Anweisung try im Abschnitt Die try Anweisung und der Anweisung raise im Abschnitt Die raise Anweisung.
4.4. Laufzeitkomponenten¶
4.4.1. Allgemeines Rechenmodell¶
Das Ausführungsmodell von Python funktioniert nicht in einem Vakuum. Es läuft auf einem Host-Rechner und nutzt dessen Laufzeitumgebung, einschließlich des Betriebssystems (OS), sofern vorhanden. Wenn ein Programm ausgeführt wird, sehen die konzeptionellen Ebenen seiner Ausführung auf dem Host in etwa wie folgt aus:
Host-RechnerProzess (globale Ressourcen)Thread (führt Maschinencode aus)
Jeder Prozess entspricht einem Programm, das auf dem Host läuft. Stellen Sie sich jeden Prozess selbst als den Datenteil seines Programms vor. Stellen Sie sich die Threads des Prozesses als den Ausführungsteil des Programms vor. Diese Unterscheidung ist wichtig, um die konzeptionelle Python-Laufzeitumgebung zu verstehen.
Der Prozess ist, ebenso wie der Datenteil, der Ausführungskontext, in dem das Programm läuft. Er besteht im Wesentlichen aus der Gesamtheit der Ressourcen, die dem Programm vom Host zugewiesen wurden, darunter Speicher, Signale, Dateihandles, Sockets und Umgebungsvariablen.
Prozesse sind voneinander isoliert und unabhängig. (Das Gleiche gilt für Hosts.) Der Host verwaltet den Zugriff des Prozesses auf die ihm zugewiesenen Ressourcen und sorgt zudem für die Koordination zwischen den Prozessen.
Jeder Thread steht für die tatsächliche Ausführung des Maschinencodes des Programms, die relativ zu den Ressourcen erfolgt, die dem Prozess des Programms zugewiesen sind. Es liegt ausschließlich im Ermessen des Hosts, wie und wann diese Ausführung stattfindet.
Aus Sicht von Python beginnt ein Programm immer mit genau einem Thread. Das Programm kann jedoch so wachsen, dass es in mehreren gleichzeitigen Threads ausgeführt wird. Nicht alle Hosts unterstützen mehrere Threads pro Prozess, die meisten jedoch schon. Im Gegensatz zu Prozessen sind Threads innerhalb eines Prozesses nicht voneinander isoliert und unabhängig. Konkret bedeutet dies, dass sich alle Threads eines Prozesses die gesamten Ressourcen des Prozesses teilen.
Das Wesentliche an Threads ist, dass jeder einzelne unabhängig von den anderen läuft, und zwar gleichzeitig mit ihnen. Das kann entweder nur konzeptionell gleichzeitig („konkurrent“) oder physisch („parallel“) der Fall sein. So oder so laufen die Threads effektiv mit einer nicht synchronisierten Geschwindigkeit.
Bemerkung
Diese nicht synchronisierte Rate bedeutet, dass für den in einem bestimmten Thread ausgeführten Code nicht garantiert werden kann, dass der Speicher des Prozesses konsistent bleibt. Daher müssen Multithread-Programme darauf achten, den Zugriff auf bewusst gemeinsam genutzte Ressourcen zu koordinieren. Ebenso müssen sie unbedingt darauf achten, in mehreren Threads nicht auf andere Ressourcen zuzugreifen; andernfalls könnten sich zwei gleichzeitig laufende Threads versehentlich bei der Nutzung gemeinsamer Daten gegenseitig stören. All dies gilt sowohl für Python-Programme als auch für die Python-Laufzeitumgebung.
Der Preis für diese weit gefasste, unstrukturierte Anforderung ist der Kompromiss, den man für die Art von roher Parallelität eingehen muss, die Threads bieten. Die Alternative zu der erforderlichen Disziplin bedeutet in der Regel, sich mit nichtdeterministischen Fehlern und Datenbeschädigungen auseinandersetzen zu müssen.
4.4.2. Python-Laufzeitmodell¶
Für jedes Python-Programm gelten dieselben konzeptionellen Ebenen, ergänzt durch einige zusätzliche, für Python spezifische Datenschichten:
Host-RechnerProzess (globale Ressourcen)Globale Laufzeitumgebung von Python (state)Python-Interpreter (Zustand)Thread (führt Python-Bytecode und „C-API“ aus)Python-Thread-Zustand
Auf konzeptioneller Ebene: Wenn ein Python-Programm startet, sieht es genau wie in diesem Diagramm aus, wobei jeweils nur ein Element vorhanden ist. Die Laufzeitumgebung kann sich so weit ausdehnen, dass sie mehrere Interpreter umfasst, und jeder Interpreter kann sich so weit ausdehnen, dass er mehrere Thread-Zustände umfasst.
Bemerkung
Eine Python-Implementierung muss die Laufzeitschichten nicht unbedingt klar voneinander abgegrenzt oder gar konkret implementieren. Die einzige Ausnahme bilden Stellen, an denen bestimmte Schichten direkt festgelegt oder den Benutzern zugänglich gemacht werden, beispielsweise über das Modul threading .
Bemerkung
Der ursprüngliche Interpreter wird in der Regel als „Haupt“-Interpreter bezeichnet. Einige Python-Implementierungen, wie beispielsweise CPython, weisen dem Hauptinterpreter besondere Rollen zu.
Ebenso wird der Host-Thread, in dem die Laufzeitumgebung initialisiert wurde, als „Hauptthread“ bezeichnet. Er kann sich vom Start-Thread des Prozesses unterscheiden, obwohl beide oft identisch sind. In manchen Fällen kann der Begriff „Hauptthread“ sogar noch spezifischer sein und sich auf den Zustand des Start-Threads beziehen. Eine Python-Laufzeitumgebung kann dem Hauptthread bestimmte Aufgaben zuweisen, wie beispielsweise die Verarbeitung von Signalen.
Insgesamt besteht die Python-Laufzeitumgebung aus dem globalen Laufzeitzustand, den Interpretern und den Thread-Zuständen. Die Laufzeitumgebung stellt sicher, dass all diese Zustände während ihrer gesamten Lebensdauer konsistent bleiben, insbesondere bei der Verwendung mit mehreren Host-Threads.
Die globale Laufzeitumgebung ist auf konzeptioneller Ebene lediglich eine Sammlung von Interpretern. Obwohl diese Interpreter ansonsten voneinander isoliert und unabhängig sind, können sie bestimmte Daten oder andere Ressourcen gemeinsam nutzen. Die Laufzeitumgebung ist dafür verantwortlich, diese globalen Ressourcen sicher zu verwalten. Die konkrete Ausgestaltung und Verwaltung dieser Ressourcen ist implementierungsspezifisch. Letztendlich beschränkt sich der externe Nutzen der globalen Laufzeitumgebung auf die Verwaltung von Interpretern.
Im Gegensatz dazu entspricht ein „Interpreter“ konzeptionell dem, was wir normalerweise unter der (voll funktionsfähigen) „Python-Laufzeitumgebung“ verstehen. Wenn Maschinencode, der in einem Host-Thread ausgeführt wird, mit der Python-Laufzeitumgebung interagiert, ruft er Python im Kontext eines bestimmten Interpreters auf.
Bemerkung
Der Begriff „Interpreter“ ist hier nicht mit dem „Bytecode-Interpreter“ gleichzusetzen, der üblicherweise in Threads läuft und kompilierten Python-Code ausführt.
In einer idealen Welt würde sich der Begriff „Python-Laufzeitumgebung“ auf das beziehen, was wir derzeit als „Interpreter“ bezeichnen. Allerdings wird er bereits seit seiner Einführung im Jahr 1997 als „Interpreter“ bezeichnet (CPython:a027efa5b).
Jeder Interpreter kapselt den gesamten nicht prozessglobalen und nicht threadspezifischen Zustand vollständig ein, der für den Betrieb der Python-Laufzeitumgebung erforderlich ist. Insbesondere bleibt der Zustand des Interpreters zwischen den einzelnen Verwendungen erhalten. Er umfasst grundlegende Daten wie sys.modules. Die Laufzeitumgebung stellt sicher, dass mehrere Threads, die denselben Interpreter verwenden, diesen sicher untereinander gemeinsam nutzen können.
Eine Python-Implementierung kann die gleichzeitige Nutzung mehrerer Interpreter innerhalb desselben Prozesses unterstützen. Diese sind voneinander unabhängig und isoliert. So verfügt beispielsweise jeder Interpreter über eine eigene Instanz von sys.modules.
Für den threadspezifischen Laufzeitstatus verfügt jeder Interpreter über eine Reihe von Thread-Zuständen, die er verwaltet – genauso wie die globale Laufzeitumgebung eine Reihe von Interpretern enthält. Er kann Thread-Zustände für so viele Host-Threads haben, wie er benötigt. Es kann sogar vorkommen, dass er mehrere Thread-Zustände für denselben Host-Thread hat, auch wenn dies nicht allzu häufig vorkommt.
Konzeptionell enthält jeder Thread-Zustand alle threadspezifischen Laufzeitdaten, die ein Interpreter benötigt, um in einem Host-Thread zu arbeiten. Der Thread-Zustand umfasst die aktuell ausgelöste Ausnahme und den Python-Aufrufstapel des Threads. Er kann auch weitere threadspezifische Ressourcen enthalten.
Bemerkung
Der Begriff „Python-Thread“ kann sich manchmal auf einen Thread-Zustand beziehen, bezeichnet jedoch in der Regel einen Thread, der mithilfe des Moduls threading erstellt wurde.
Jeder Thread-Zustand ist während seiner gesamten Lebensdauer stets an genau einen Interpreter und genau einen Host-Thread gebunden. Er wird ausschließlich in diesem Thread und mit diesem Interpreter verwendet.
Einem Host-Thread können mehrere Thread-Zustände zugeordnet sein, sei es für verschiedene Interpreter oder sogar für denselben Interpreter. Für einen bestimmten Host-Thread kann jedoch jeweils nur einer der ihm zugeordneten Thread-Zustände von diesem Thread verwendet werden.
Die Zustände der Threads sind voneinander isoliert und unabhängig und tauschen keine Daten aus, abgesehen von der möglichen gemeinsamen Nutzung eines Interpreters sowie von Objekten oder anderen Ressourcen, die zu diesem Interpreter gehören.
Once a program is running, new Python threads can be created using the
threading module (on platforms and Python implementations that
support threads). Additional processes can be created using the
os, subprocess, and multiprocessing modules.
Coroutines (async) can
be run using asyncio in each interpreter, typically only
in a single thread (often the main thread).
Fußnoten