4. Ausführungsmodell¶
4.1. Struktur eines Programms¶
Ein Python-Programm besteht aus Codeblöcken. Ein Block ist ein Teil eines Python-Programmtextes, der als Einheit ausgeführt wird. Folgende Einheiten sind Blöcke: ein Modul, ein Funktionsrumpf und eine Klassendefinition. Jeder interaktiv eingegebene Befehl ist ein Block. Eine Skriptdatei (eine Datei, die dem Interpreter als Standardeingabe übergeben oder als Kommandozeilenargument übergeben wird) ist ein Codeblock. Ein Skriptbefehl (ein mit der Option -c übergebener Befehl) ist ein Codeblock. Ein als Top-Level-Skript (als Modul __main__) über die Kommandozeile mittels eines -m-Arguments gestartetes Modul ist ebenfalls ein Codeblock. Die Zeichenkette, die den eingebauten Funktionen eval() und exec() übergeben wird, ist ein Codeblock.
Ein Codeblock wird in einem Ausführungsframe ausgeführt. Ein Frame enthält einige administrative Informationen (für das Debugging) und bestimmt, wo und wie die Ausführung fortgesetzt wird, nachdem die Ausführung des Codeblocks abgeschlossen ist.
4.2. Benennung und Bindung¶
4.2.1. Bindung von Namen¶
Namen verweisen auf Objekte. Namen werden durch Namensbindungsoperationen eingeführt.
Die folgenden Konstrukte binden Namen
formale Parameter von Funktionen,
Klassendefinitionen,
Funktionsdefinitionen,
Zuweisungsausdrücke,
Ziele, die Identifikatoren sind, wenn sie in einer Zuweisung vorkommen
import-Anweisungen.type-Anweisungen.
Die import-Anweisung der Form from ... import * bindet alle Namen, die im importierten Modul definiert sind, ausgenommen jene, die mit einem Unterstrich beginnen. Diese Form darf nur auf Modulebene verwendet werden.
Ein Ziel, das in einer del-Anweisung vorkommt, wird zu diesem Zweck ebenfalls als gebunden betrachtet (obwohl die tatsächliche Semantik darin besteht, den Namen zu entbinden).
Jede Zuweisungs- oder Importanweisung kommt innerhalb eines Blocks vor, der durch eine Klassen- oder Funktionsdefinition oder auf Modulebene (dem obersten Codeblock) definiert wird.
Wenn ein Name in einem Block gebunden wird, ist er eine lokale Variable dieses Blocks, es sei denn, er wird als nonlocal oder global deklariert. Wenn ein Name auf Modulebene gebunden wird, ist er eine globale Variable. (Die Variablen des Modul-Codeblocks sind lokal und global.) Wenn eine Variable in einem Codeblock verwendet, dort aber nicht definiert wird, ist sie eine freie Variable.
Jedes Vorkommen eines Namens im Programmtext verweist auf die Bindung dieses Namens, die durch die folgenden Regeln zur Namensauflösung hergestellt wird.
4.2.2. Auflösung von Namen¶
Ein Geltungsbereich (Scope) definiert die Sichtbarkeit eines Namens innerhalb eines Blocks. Wenn eine lokale Variable in einem Block definiert wird, umfasst ihr Geltungsbereich diesen Block. Wenn die Definition in einem Funktionsblock erfolgt, erstreckt sich der Geltungsbereich auf alle darin enthaltenen Blöcke, es sei denn, ein 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 umschließenden Geltungsbereichs aufgelöst. Die Gesamtheit aller solcher Geltungsbereiche, die für einen Codeblock sichtbar sind, wird als dessen Umgebung bezeichnet.
Wenn ein Name überhaupt nicht gefunden wird, wird eine NameError-Ausnahme ausgelöst. Wenn der aktuelle Geltungsbereich ein Funktionsgeltungsbereich ist und der Name sich auf eine lokale Variable bezieht, die zum Zeitpunkt der Verwendung des Namens noch nicht an einen Wert gebunden wurde, wird eine UnboundLocalError-Ausnahme ausgelöst. UnboundLocalError ist eine Unterklasse von NameError.
Wenn eine Namensbindungsoperation irgendwo innerhalb eines Codeblocks 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 subtil. Python hat keine Deklarationen und erlaubt Namensbindungsoperationen an beliebiger Stelle innerhalb eines Codeblocks. Die lokalen Variablen eines Codeblocks können bestimmt werden, indem der gesamte Text des Blocks nach Namensbindungsoperationen durchsucht wird. Beispiele finden sich im FAQ-Eintrag zu UnboundLocalError.
Wenn die global-Anweisung innerhalb eines Blocks vorkommt, beziehen sich alle Verwendungen der in der Anweisung genannten Namen auf die Bindungen dieser Namen im Top-Level-Namensraum. Namen werden im Top-Level-Namensraum aufgelöst, indem der globale Namensraum, d. h. der Namensraum des Moduls, das den Codeblock enthält, und der Builtins-Namensraum, der Namensraum des Moduls builtins, durchsucht werden. Der globale Namensraum wird zuerst durchsucht. Werden die Namen dort nicht gefunden, wird der Builtins-Namensraum als Nächstes durchsucht. Werden die Namen auch im Builtins-Namensraum nicht gefunden, werden neue Variablen im globalen Namensraum erstellt. Die globale Anweisung muss vor allen Verwendungen der aufgeführten Namen stehen.
Die global-Anweisung hat denselben Geltungsbereich wie eine Namensbindungsoperation im selben Block. Wenn der nächstgelegene umschließende Geltungsbereich einer freien Variablen eine globale Anweisung enthält, wird die freie Variable als global behandelt.
Die nonlocal-Anweisung bewirkt, dass sich entsprechende Namen auf zuvor gebundene Variablen im nächstgelegenen umschließenden Funktionsgeltungsbereich beziehen. Eine SyntaxError wird zur Kompilierzeit ausgelöst, wenn der gegebene Name in keinem umschließenden Funktionsgeltungsbereich existiert. Typ-Parameter können nicht mit der nonlocal-Anweisung neu gebunden werden.
Der Namensraum für ein Modul wird automatisch erstellt, sobald ein Modul zum ersten Mal importiert wird. Das Hauptmodul eines Skripts heißt immer __main__.
Klassen-Definitionsblöcke und Argumente für exec() und eval() sind im Kontext der Namensauflösung besonders. Eine Klassendefinition ist eine ausführbare Anweisung, die Namen verwenden und definieren kann. Diese Referenzen folgen den normalen Regeln für die Namensauflösung mit der Ausnahme, dass ungebundene lokale Variablen im globalen Namensraum nachgeschlagen werden. Der Namensraum der Klassendefinition wird zum Attributwörterbuch der Klasse. Der Geltungsbereich von Namen, die in einem Klassenblock definiert sind, ist auf den Klassenblock beschränkt; er erstreckt sich nicht auf die Codeblöcke von Methoden. Dies schließt Comprehensions und Generatorausdrücke ein, aber es schließt keine Annotations-Geltungsbereiche ein, die Zugriff auf ihre umschließenden Klassengeltungsbereiche haben. Das bedeutet, dass das Folgende fehlschlägt
class A:
a = 42
b = list(a + i for i in range(10))
Das Folgende wird jedoch erfolgreich sein
class A:
type Alias = Nested
class Nested: pass
print(A.Alias.__value__) # <type 'A.Nested'>
4.2.3. Annotations-Geltungsbereiche¶
Annotationen, Typ-Parameterlisten und type-Anweisungen führen Annotations-Geltungsbereiche ein, die sich größtenteils wie Funktionsgeltungsbereiche verhalten, jedoch mit einigen unten diskutierten Ausnahmen.
Annotations-Geltungsbereiche 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 Annotations-Geltungsbereichs ausgeführt, ihre Standardwerte und Dekoratoren jedoch nicht.
Typ-Parameterlisten für generische Klassen. Die Basisklassen und Schlüsselwortargumente einer generischen Klasse werden innerhalb des Annotations-Geltungsbereichs ausgeführt, ihre Dekoratoren jedoch nicht.
Die Grenzen, Einschränkungen und Standardwerte für Typ-Parameter (lazy evaluiert).
Die Werte von Typ-Aliasen (lazy evaluiert).
Annotations-Geltungsbereiche unterscheiden sich in den folgenden Punkten von Funktionsgeltungsbereichen
Annotations-Geltungsbereiche haben Zugriff auf ihren umschließenden Klassennamensraum. Wenn ein Annotations-Geltungsbereich sich unmittelbar innerhalb eines Klassengeltungsbereichs befindet oder innerhalb eines anderen Annotations-Geltungsbereichs, der sich unmittelbar innerhalb eines Klassengeltungsbereichs befindet, kann der Code im Annotations-Geltungsbereich Namen aus dem Klassengeltungsbereich verwenden, so als wäre er direkt im Klassenrumpf ausgeführt worden. Dies steht im Gegensatz zu regulären Funktionen, die innerhalb von Klassen definiert werden und nicht auf Namen zugreifen können, die im Klassengeltungsbereich definiert sind.
Ausdrücke in Annotations-Geltungsbereichen dürfen keine
yield-,yield from-,await- oder:=-Ausdrücke enthalten. (Diese Ausdrücke sind in anderen, innerhalb des Annotations-Geltungsbereichs enthaltenen Geltungsbereichen erlaubt.)Namen, die in Annotations-Geltungsbereichen definiert sind, können mit
nonlocal-Anweisungen in inneren Geltungsbereichen nicht neu gebunden werden. Dies betrifft nur Typ-Parameter, da keine anderen syntaktischen Elemente, die innerhalb von Annotations-Geltungsbereichen vorkommen können, neue Namen einführen.Während Annotations-Geltungsbereiche einen internen Namen haben, wird dieser Name nicht im qualifizierten Namen von innerhalb des Geltungsbereichs definierten Objekten widergespiegelt. Stattdessen ist der
__qualname__solcher Objekte so, als wäre das Objekt im umschließenden Geltungsbereich definiert worden.
Hinzugefügt in Version 3.12: Annotations-Geltungsbereiche wurden in Python 3.12 als Teil von PEP 695 eingeführt.
Geändert in Version 3.13: Annotations-Geltungsbereiche werden jetzt auch für Typ-Parameter-Standardwerte verwendet, wie eingeführt durch PEP 696.
4.2.4. Lazy Evaluation¶
Die meisten Annotations-Geltungsbereiche sind lazy evaluiert. Dazu gehören Annotationen, die Werte von Typ-Aliasen, die über die type-Anweisung erstellt werden, sowie die Grenzen, Einschränkungen und Standardwerte von Typvariablen, die über die Typ-Parameter-Syntax erstellt werden. Das bedeutet, dass sie nicht ausgewertet werden, wenn der Typ-Alias oder die Typvariable erstellt wird oder wenn das Objekt, das Annotationen trägt, erstellt wird. Stattdessen werden sie nur ausgewertet, wenn es notwendig ist, z. B. wenn auf das Attribut __value__ eines Typ-Alias zugegriffen wird.
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 dann ausgelöst, wenn auf das Attribut __value__ des Typ-Alias oder das Attribut __bound__ der Typvariable zugegriffen wird.
Dieses Verhalten ist hauptsächlich nützlich für Referenzen auf Typen, die noch nicht definiert wurden, wenn der Typ-Alias oder die Typvariable erstellt wird. Lazy Evaluation ermöglicht beispielsweise die Erstellung von gegenseitig rekursiven Typ-Aliasen
from typing import Literal
type SimpleExpr = int | Parenthesized
type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]
Lazy evaluierte Werte werden in einem Annotations-Geltungsbereich evaluiert, was bedeutet, dass Namen, die innerhalb des lazy evaluierten Wertes auftreten, so nachgeschlagen werden, als würden sie im unmittelbar umschließenden Geltungsbereich verwendet.
Hinzugefügt in Version 3.12.
4.2.5. Builtins und eingeschränkte Ausführung¶
CPython-Implementierungsdetail: Benutzer sollten __builtins__ nicht anfassen; es ist streng ein Implementierungsdetail. Benutzer, die Werte im Builtins-Namensraum überschreiben möchten, sollten das Modul builtins importieren und dessen Attribute entsprechend ändern.
Der Builtins-Namensraum, der mit der Ausführung eines Codeblocks assoziiert ist, wird tatsächlich gefunden, indem der Name __builtins__ in seinem globalen Namensraum nachgeschlagen wird; dies sollte ein Wörterbuch oder ein Modul sein (im letzteren Fall wird das Wörterbuch des Moduls verwendet). Standardmäßig, wenn sich der Interpreter im Modul __main__ befindet, ist __builtins__ das eingebaute Modul builtins; wenn sich der Interpreter in einem anderen Modul befindet, ist __builtins__ ein Alias für das Wörterbuch des Moduls builtins selbst.
4.2.6. Interaktion mit dynamischen Features¶
Die Namensauflösung von freien Variablen erfolgt zur Laufzeit, nicht zur Kompilierzeit. 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 Auflösung von Namen. Namen können im lokalen und globalen Namensraum des Aufrufers aufgelöst werden. Freie Variablen werden nicht im nächstgelegenen umschließenden Namensraum, sondern im globalen Namensraum aufgelöst. [1] Die Funktionen exec() und eval() haben optionale Argumente, um den globalen und lokalen Namensraum zu überschreiben. Wenn nur ein Namensraum angegeben wird, wird dieser für beide verwendet.
4.3. Ausnahmen¶
Ausnahmen sind ein Mittel, um den normalen Kontrollfluss eines Codeblocks zu unterbrechen, um Fehler oder andere außerordentliche Bedingungen zu behandeln. Eine Ausnahme wird an der Stelle, an der der Fehler erkannt wird, ausgelöst (raised); sie kann vom umgebenden Codeblock oder von einem beliebigen Codeblock, der den Codeblock, in dem der Fehler aufgetreten ist, direkt oder indirekt aufgerufen hat, behandelt (handled) werden.
Der Python-Interpreter löst eine Ausnahme aus, wenn er einen Laufzeitfehler erkennt (z. B. Division durch Null). Ein Python-Programm kann eine Ausnahme auch explizit mit der raise-Anweisung auslösen. Ausnahmebehandler werden mit der try … except-Anweisung spezifiziert. Die finally-Klausel einer solchen Anweisung kann verwendet werden, um Bereinigungscode anzugeben, der die Ausnahme nicht behandelt, aber unabhängig davon ausgeführt wird, ob im vorhergehenden Code eine Ausnahme aufgetreten ist oder nicht.
Python verwendet das „Terminierungsmodell“ der Fehlerbehandlung: Ein Ausnahmebehandler kann herausfinden, was passiert ist, und die Ausführung auf einer äußeren Ebene fortsetzen, aber er kann nicht die Ursache des Fehlers reparieren und die fehlgeschlagene Operation wiederholen (außer durch erneutes Betreten des fehlerhaften Codeteils von Anfang an).
Wenn eine Ausnahme überhaupt nicht behandelt wird, beendet der Interpreter die Programmausführung oder kehrt zu seiner interaktiven Hauptschleife zurück. In beiden Fällen gibt er eine Stack-Traceback aus, es sei denn, die Ausnahme ist SystemExit.
Ausnahmen werden durch Klasseninstanzen identifiziert. Die except-Klausel wird je nach Klasse der Instanz ausgewählt: sie muss sich auf die Klasse der Instanz oder eine nicht-virtuelle Basisklasse davon beziehen. Die Instanz kann vom Handler empfangen werden und zusätzliche Informationen über die außergewöhnliche Bedingung tragen.
Hinweis
Ausnahmen-Nachrichten sind kein Teil der Python-API. Ihr Inhalt kann sich von einer Python-Version zur nächsten ohne Vorwarnung ändern und sollte nicht von Code verwendet werden, der unter mehreren Interpreter-Versionen ausgeführt wird.
Siehe auch die Beschreibung der try-Anweisung in Abschnitt Die try-Anweisung und der raise-Anweisung in Abschnitt Die raise-Anweisung.
4.4. Laufzeitkomponenten¶
4.4.1. Allgemeines Berechnungsmodell¶
Das Ausführungsmodell von Python läuft nicht im Vakuum. Es läuft auf einer Host-Maschine und über die Laufzeitumgebung dieser Host-Maschine, einschließlich ihres Betriebssystems (OS), falls vorhanden. Wenn ein Programm ausgeführt wird, sehen die konzeptionellen Schichten, wie es auf dem Host läuft, ungefähr so aus
Host-MaschineProzess (globale Ressourcen)Thread (führt Maschinencode aus)
Jeder Prozess repräsentiert ein auf dem Host laufendes Programm. Stellen Sie sich jeden Prozess 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 das konzeptionelle Python-Laufzeitmodell zu verstehen.
Der Prozess ist als Datenteil der Ausführungskontext, in dem das Programm läuft. Er besteht hauptsächlich aus der Menge der vom Host dem Programm zugewiesenen Ressourcen, einschließlich Speicher, Signale, Dateihandles, Sockets und Umgebungsvariablen.
Prozesse sind voneinander isoliert und unabhängig. (Dasselbe gilt für Hosts.) Der Host verwaltet den Zugriff des Prozesses auf seine zugewiesenen Ressourcen und koordiniert die Prozesse untereinander.
Jeder Thread repräsentiert die tatsächliche Ausführung des Maschinencodes des Programms, laufend relativ zu den dem Prozess des Programms zugewiesenen Ressourcen. Es liegt ausschließlich am Host, wie und wann diese Ausführung stattfindet.
Aus der Sicht von Python beginnt ein Programm immer mit genau einem Thread. Das Programm kann jedoch wachsen, um in mehreren gleichzeitigen Threads zu laufen. Nicht alle Hosts unterstützen mehrere Threads pro Prozess, aber die meisten tun es. Im Gegensatz zu Prozessen sind Threads in einem Prozess nicht isoliert und unabhängig voneinander. Insbesondere teilen sich alle Threads in einem Prozess alle Ressourcen des Prozesses.
Der fundamentale Punkt von Threads ist, dass jeder von ihnen unabhängig läuft, gleichzeitig mit den anderen. Das kann nur konzeptionell gleichzeitig sein („konkurrent“) oder physisch („parallel“). In beiden Fällen laufen die Threads effektiv mit einer nicht synchronisierten Rate.
Hinweis
Diese nicht synchronisierte Rate bedeutet, dass kein Speicher des Prozesses garantiert konsistent für den Code bleibt, der in einem bestimmten Thread läuft. Daher müssen Multithreaded-Programme darauf achten, den Zugriff auf absichtlich geteilte Ressourcen zu koordinieren. Ebenso müssen sie sorgfältig darauf achten, keine anderen Ressourcen in mehreren Threads zu verwenden; andernfalls könnten zwei gleichzeitig laufende Threads versehentlich die Verwendung einiger geteilter Daten beeinträchtigen. Dies gilt sowohl für Python-Programme als auch für die Python-Laufzeitumgebung.
Die Kosten dieser breiten, unstrukturierten Anforderung sind der Kompromiss für die Art der rohen Nebenläufigkeit, die Threads bieten. Die Alternative zum erforderlichen Disziplin beinhaltet im Allgemeinen den Umgang mit nicht-deterministischen Fehlern und Datenbeschädigung.
4.4.2. Python-Laufzeitmodell¶
Dieselbe konzeptionelle Schichtung gilt für jedes Python-Programm, mit einigen zusätzlichen Datenschichten, die spezifisch für Python sind
Host-MaschineProzess (globale Ressourcen)Python globale Laufzeit (Zustand)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 der Abbildung aus, mit einem von jedem. Die Laufzeit kann wachsen, um mehrere Interpreter zu enthalten, und jeder Interpreter kann wachsen, um mehrere Thread-Zustände zu enthalten.
Hinweis
Eine Python-Implementierung wird die Laufzeitschichten nicht unbedingt distinkt oder sogar konkret implementieren. Die einzige Ausnahme sind Stellen, an denen distinkte Schichten direkt spezifiziert oder für Benutzer zugänglich gemacht werden, z. B. über das Modul threading.
Hinweis
Der initiale Interpreter wird typischerweise der „Haupt“-Interpreter genannt. Einige Python-Implementierungen, wie CPython, weisen dem Hauptinterpreter spezielle Rollen zu.
Ebenso wird der Host-Thread, in dem die Laufzeitumgebung initialisiert wurde, als „main“-Thread bezeichnet. Er kann sich vom initialen Thread des Prozesses unterscheiden, obwohl sie oft identisch sind. In einigen Fällen kann „main thread“ noch spezifischer sein und sich auf den initialen Thread-Zustand beziehen. Eine Python-Laufzeitumgebung kann dem main-Thread spezifische Verantwortlichkeiten zuweisen, wie z. B. die Verarbeitung von Signalen.
Insgesamt besteht die Python-Laufzeitumgebung aus dem globalen Laufzeit-Zustand, Interpretern und Thread-Zuständen. Die Laufzeitumgebung stellt sicher, dass all diese Zustände während ihrer Lebensdauer konsistent bleiben, insbesondere wenn sie mit mehreren Host-Threads verwendet wird.
Die globale Laufzeitumgebung ist auf konzeptioneller Ebene nur eine Menge von Interpretern. Während diese Interpreter ansonsten isoliert und voneinander unabhängig sind, können sie einige Daten oder andere Ressourcen gemeinsam nutzen. Die Laufzeitumgebung ist für die sichere Verwaltung dieser globalen Ressourcen verantwortlich. Die tatsächliche Natur und Verwaltung dieser Ressourcen ist implementierungsspezifisch. Letztendlich beschränkt sich der externe Nutzen der globalen Laufzeitumgebung auf die Verwaltung von Interpretern.
Im Gegensatz dazu ist ein „Interpreter“ konzeptionell das, was wir normalerweise als die (voll ausgestattete) „Python-Laufzeitumgebung“ bezeichnen würden. Wenn Maschinencode, der in einem Host-Thread ausgeführt wird, mit der Python-Laufzeitumgebung interagiert, ruft er Python im Kontext eines bestimmten Interpreters auf.
Hinweis
Der Begriff „Interpreter“ ist hier nicht dasselbe wie der „Bytecode-Interpreter“, der regelmäßig in Threads läuft und kompilierten Python-Code ausführt.
In einer idealen Welt würde sich „Python runtime“ auf das beziehen, was wir derzeit „interpreter“ nennen. Es wird jedoch zumindest seit seiner Einführung im Jahr 1997 als „interpreter“ bezeichnet (CPython:a027efa5b).
Jeder Interpreter kapselt vollständig alle nicht-prozessglobalen, nicht-threadspezifischen Zustände, die für die Funktion der Python-Laufzeitumgebung erforderlich sind. Bemerkenswerterweise bleibt der Zustand des Interpreters zwischen den Verwendungen erhalten. Er umfasst grundlegende Daten wie sys.modules. Die Laufzeitumgebung stellt sicher, dass mehrere Threads, die denselben Interpreter verwenden, ihn sicher gemeinsam nutzen.
Eine Python-Implementierung kann die gleichzeitige Verwendung mehrerer Interpreter im selben Prozess unterstützen. Sie sind unabhängig und voneinander isoliert. Beispielsweise hat jeder Interpreter seine eigenen sys.modules.
Für threadspezifische Laufzeit-Zustände hat jeder Interpreter einen Satz von Thread-Zuständen, die er verwaltet, so wie die globale Laufzeitumgebung einen Satz von Interpretern enthält. Er kann Thread-Zustände für so viele Host-Threads haben, wie er benötigt. Er kann sogar mehrere Thread-Zustände für denselben Host-Thread haben, obwohl dies weniger üblich ist.
Jeder Thread-Zustand hat konzeptionell 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-Aufrufstack des Threads. Er kann andere threadspezifische Ressourcen umfassen.
Hinweis
Der Begriff „Python thread“ kann sich manchmal auf einen Thread-Zustand beziehen, aber normalerweise bedeutet er einen Thread, der mit dem threading-Modul erstellt wurde.
Jeder Thread-Zustand ist während seiner Lebensdauer immer genau einem Interpreter und genau einem Host-Thread zugeordnet. Er wird immer nur in diesem Thread und mit diesem Interpreter verwendet.
Mehrere Thread-Zustände können demselben Host-Thread zugeordnet sein, sei es für verschiedene Interpreter oder sogar für denselben Interpreter. Für einen gegebenen Host-Thread kann jedoch zu einem bestimmten Zeitpunkt nur einer der ihm zugeordneten Thread-Zustände vom Thread verwendet werden.
Thread-Zustände sind voneinander isoliert und unabhängig und teilen keine Daten, außer möglicherweise die gemeinsame Nutzung eines Interpreters und von Objekten oder anderen Ressourcen, die zu diesem Interpreter gehören.
Sobald ein Programm ausgeführt wird, können neue Python-Threads mit dem threading-Modul (auf Plattformen und Python-Implementierungen, die Threads unterstützen) erstellt werden. Zusätzliche Prozesse können mit den Modulen os, subprocess und multiprocessing erstellt werden. Interpreter können mit dem Modul interpreters erstellt und verwendet werden. Coroutinen (async) können mit asyncio in jedem Interpreter ausgeführt werden, typischerweise nur in einem einzigen Thread (oft dem main-Thread).
Fußnoten