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:

  * "for" Schleifenkopf,

  * nach "as" in einer "with" -Anweisung, einer "except" -Klausel,
    einer "except*" -Klausel oder im "as"-Muster beim strukturellen
    Musterabgleich,

  * in einem Erfassungsmuster beim strukturellen Musterabgleich

* "import" Aussagen.

* "type" Aussagen.

* Typ-Parameterlisten.

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", "await" oder ":=" 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-Rechner**
         **Prozess** (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-Rechner**
         **Prozess** (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 ]-

[1] Diese Einschränkung ergibt sich daraus, dass der Code, der durch
    diese Operationen ausgeführt wird, zum Zeitpunkt der Kompilierung
    des Moduls noch nicht verfügbar ist.
