Interpretieren

Interpretieren#

Dieses Kapitel ist Expertenwissen und vertieft das Verständnis, wie Python-Code tatsächlich ausgeführt wird. Es zeigt Schritt für Schritt, wie aus Quellcode letztendlich Maschinencode („0 und 1“) wird.

Wir verwenden durchgehend das folgende einfache Beispiel:

b = 10
a = b + 3

0. Quelltext (Input)

Problem: Nur Zeichen/Text – keine Struktur, keine Bedeutung.

"b = 10\na = b + 3"

Der Quellcode ist zunächst nur eine Zeichenkette ohne strukturelle Information.

1. Lexikalische Analyse (Lexing)

Problem: Leerzeichen/Zeilenumbrüche/Kommentare sind Formatierung; der Rechner braucht bedeutungstragende Einheiten.

Lösung: Text → Tokenliste.

NAME(b) ASSIGN NUMBER(10)
NAME(a) ASSIGN NAME(b) PLUS NUMBER(3)

Die lexikalische Analyse zerlegt den Text in einzelne Tokens (Wörter/Symbole), die eine Bedeutung haben.

2. Syntaxanalyse (Parsing)

Problem: Tokenliste ist flach; Struktur (was gehört zusammen?) fehlt.

Lösung: Grammatik anwenden → AST (Abstract Syntax Tree).

Module
├── Assign: b = Const(10)
└── Assign
    ├── Name("a")
    └── Add
        ├── Name("b")
        └── Const(3)

Der Parser erstellt einen abstrakten Syntaxbaum, der die hierarchische Struktur des Programms darstellt.

3. (Leicht) Semantik / Namensauflösung

Problem: Namen müssen auf Werte zeigen (b existiert?) und Operatoren müssen zu Typen passen.

Lösung: Symboltabelle/Laufzeitumgebung wird vorbereitet (b wird angelegt).

Symboltabelle nach ‚b = 10‘:

b → int(10)

In dieser Phase wird geprüft, ob alle verwendeten Namen definiert sind und ob die Operationen für die beteiligten Typen gültig sind.

4. AST → Python-Bytecode

Warum: Bytecode ist linear und kann effizient in einer Schleife ausgeführt werden (statt AST-Baum ständig zu traversieren).

Wichtig: Bytecode ist kein CPU-Maschinencode.

# stark vereinfacht, sinngemäß
LOAD_CONST 10
STORE_NAME b
LOAD_NAME b
LOAD_CONST 3
BINARY_ADD
STORE_NAME a

Der AST wird in eine lineare Folge von Bytecode-Anweisungen umgewandelt, die effizient ausgeführt werden können.

5. Ausführung des Bytecodes (Interpreter-Loop)

Problem: Bytecode ist nur eine Liste von OpCodes – jemand muss sie abarbeiten.

Lösung: Der Python-Interpreter (python.exe) läuft als Programm und interpretiert die OpCodes.

Stack-basierte Ausführung:

Stack: []
LOAD_CONST 10     → Stack [10]
STORE_NAME b      → b=10, Stack []
LOAD_NAME b       → Stack [10]
LOAD_CONST 3      → Stack [10, 3]
BINARY_ADD        → Stack [13]
STORE_NAME a      → a=13, Stack []

Der Interpreter verwendet einen Stack (Stapel), um Werte zwischenzuspeichern und Operationen durchzuführen.

6. Mapping: OpCode → Interpreter-Funktion (C-Code)

Problem: Was bedeutet BINARY_ADD? (Welche Operation ist gemeint?)

Lösung: Im Interpreter-Code gibt es eine Fallunterscheidung: OpCode BINARY_ADD ruft eine Additionsroutine auf.

switch(opcode):
    case BINARY_ADD:
        right = pop()
        left = pop()
        result = PyNumber_Add(left, right)
        push(result)

Jeder Bytecode-OpCode wird auf eine entsprechende Funktion im Interpreter (geschrieben in C) gemappt.

7. Wo kommen die 0 und 1 her?

Kernaussage: Die 0 und 1 gehören zum Interpreter, nicht zu deinem Python-Programm.

Warum: python.exe wurde vorher (z. B. aus C) kompiliert. Der Maschinenbefehl für „addieren“ ist darin enthalten.

Vereinfachter Ablauf:

C im Interpreter:  long r = left + right;
                   ↓ kompiliert zu CPU-Befehl
Assembler:         ADD rax, rbx
                   ↓ als Bytes / Bits
Bytes (Hex):       48 01 D8
Bits (0/1):        01001000 00000001 11011000

Kernbotschaft (zum Merken)

Beim Interpretieren wird dein Programm nicht zu Maschinencode. Stattdessen führt die CPU den Maschinencode des Interpreters aus; dein Programm bestimmt nur, welche Interpreter-Routinen (z. B. Addition) ausgeführt werden.

Der Python-Interpreter selbst ist als kompiliertes Programm vorhanden und enthält bereits alle Maschinenbefehle. Ihr Python-Code wird nicht direkt zu Maschinencode kompiliert, sondern steuert, welche Routinen im Interpreter aufgerufen werden.

Hinweis: Zusammenhang mit dem Sprachverarbeitenden System

Die Schritte 1-4 entsprechen der Compiler-Funktionalität in der Abbildung Abbildung 3.2 (siehe Kapitel Interpretation). Die Schritte 5-6 entsprechen den Prozessschritten des Interpreters.