navigation Navigation


Inhaltsverzeichnis

compile()


Die Funktion compile() übersetzt Python-Code aus Strings oder Dateien in ausführbare Bytecode-Objekte. Sie bildet die Brücke zwischen dynamischer Code-Erzeugung und sicherer Ausführung, etwa für Skript-Engines, DSLs oder on-the-fly generierten Code. Dabei lassen sich Modi wie exec, eval und single wählen, um mehrzeilige Blöcke, Ausdrücke oder interaktive Snippets passend zu behandeln. Eine saubere Fehlerbehandlung über Ausnahmen und der bewusste Umgang mit filename und mode sind entscheidend, um nachvollziehbare Tracebacks und kontrollierte Ausführungsumgebungen zu erhalten.

Inhaltsverzeichnis

    Einführung

    Die compile() Funktion ist eine eingebaute Python-Funktion, die zur Metaprogrammierung und dynamischen Code-Ausführung verwendet wird. Sie ermöglicht es, Python-Quellcode oder AST (Abstract Syntax Tree) in Code-Objekte zu kompilieren, die später effizient ausgeführt werden können.

    Python ist eine interpretierte Sprache, aber tatsächlich wird Python in zwei Phasen vorbereitet.

    1. Kompilierungsphase: Der Quellcode wird in Bytecode kompiliert
    2. Ausführungsphase: Der Bytecode wird von der Python Virtual Machine ausgeführt

    Normalerweise geschehen diese beiden Schritte automatisch, wenn man exec() oder eval() verwendet. Allerdings hat dies einen Nachteil: Bei mehrfacher Ausführung desselben Codes wird jedes Mal neu kompiliert.

    Die compile() Funktion löst an dieser Stelle folgende Probleme:

    • Performance-Optimierung: Code muss nur einmal kompiliert werden, kann aber mehrfach ausgeführt werden
    • Fehlerprüfung: Syntaxfehler können bereits vor der Ausführung erkannt werden
    • Code-Analyse: Ermöglicht Zugriff auf Code-Objekte für Meta-Programmierung
    • Flexibilität: Verschiedene Ausführungsmodi für unterschiedliche Anwendungsfälle

    Syntax

    Syntax
    compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

    Parameter

    source

    Der zu kompilierende Quellcode. Dieser Parameter akzeptiert drei verschiedene Typen:

    • String: Python-Quellcode als String
    • Bytes-Objekt: Python-Quellcode als Bytes (z.B. aus einer Datei gelesen)
    • AST-Objekt: Ein Abstract Syntax Tree Objekt

    filename

    Ein String, der den Dateinamen angibt, aus dem der Code stammt. Dieser Parameter dient hauptsächlich der Fehlerberichterstattung - wenn ein Fehler auftritt, wird dieser Dateiname in der Traceback-Meldung angezeigt.


    mode

    Dieser Parameter bestimmt, welche Art von Code kompiliert wird. Es gibt drei verschiedene Modi:

    • exec - Ausführungsmodus für Statements und Module
    • eval - Evaluierungsmodus für einzelne Ausdrücke
    • single - Einzelanweisungsmodus für eine einzelne interaktive Anweisung

    flags (optional)

    Dieser Parameter steuer verschiedene Compiler-Optionen durch bitweise Flags.

    Standard: 0


    dont_inherit (optional)

    Bestimmt, ob Compiler-Flags vom aktuellen Kontext geerbt werden sollen.

    Standard: False


    optimize

    Steuert die Optimierungsstufe des Compilers.



    Schauen wir uns nun bestimmte Parameter etwas genauer an.

    Parameter source

    Das ist der Code, welcher kompiliert werden soll und dieser Parameter gibt an, woher dieser Code kommt.

    Beispiel
    import ast
    
    # Quelle als String
    source_1 = "x = 10\nprint(x)"
    
    # Quelle als Bytes
    source_2 = b"x = 20\nprint(x)"
    
    # Quelle als AST
    source_3 = ast.parse("x = 30")

    Parameter filename

    Bei diesem Parameter gibt es folgende Konventionen.

    • Wenn der Code aus einer tatsächlichen Datei stammt: Verwende den echten Dateinamen
    • Wenn der Code dynamisch erzeugt wird: Verwende beschreibende Namen wie <string>, <input>, <dynamic>
    • Der Standard in interaktiven Umgebungen ist of <stdin>
    Beispiel
    code = compile("print('Hallo')", filename="my_script.py", mode="exec")

    Bei Fehlern erscheint my_script.py in der Traceback-Meldung.


    Parameter mode

    Dieser Parameter gibt an, wie der Code kompiliert werden soll.

    Option exec

    Diese Option wird bei Ausführungsmodus verwendet.

    • Kann mehrere Zeilen Code enthalten
    • Wird mit exec() ausgeführt
    • Gibt standardmäßig None zurück
    • Verwendet für: Scripte, Module, mehrere Anweisungen
    Beispiel
    code = compile("x = 5\ny = 10\nprint(x + y)", filename="<string>", mode="exec")

    Option eval

    Diese Option wird für Evaluierungsmodus verwendet.

    • Kann nur einen einzigen Ausdruck (Expression) enthalten (z.B. 2 + 2, x * 10)
    • Wird mit eval() ausgeführt
    • Gibt den Wert der Expression zurück
    • Verwendet für: Berechnungen, Auswertungen
    Beispiel
    code = compile("2 + 2", filename="<string>", mode="eval")

    Option single

    Diese Option ist für Einzelanweisungsmodus verantwortlich.

    • Verhält sich wie die interaktive Python-Shell
    • Gibt None zurück, aber druckt das Ergebnis von Ausdrücken automatisch
    • Wird mit exec() ausgeführt
    • Verwendet für : REPL-ähnliche Umgebungen
    Beispiel
    code = compile("2 + 2", filename="<string>", mode="single")

    Parameter flags

    Dieser Option steuert verschiedene Compiler-Optionen durch bitweise Flags. Die Flags werden aus dem __future__ Modul importiert und kontrollieren, welche zukünftigen Sprachfeatures aktiviert werden sollen.

    Verfügbare Flags (Beispiele)

    • division.compiler_flag: Aktiviert True Division (3/2 = 1.5 statt 1)
    • print_function.compiler_flag: print als Funktion statt Statement
    • unicode_literals.compiler_flag: String-Literale sind Unicode
    • absolute_import.compiler_flag: Absolute Imports standardmäßig
    Beispiel
    flags = compile("print(3/2)", "<string>", "eval", flags=division.compiler_flag | print_function.compiler_flag)

    Parameter dont_inherit

    Diese Option bestimmt, ob Compiler-Flags vom aktuellen Kontext geerbt werden sollen.

    • False (Standard): Flags werden vom aufrufenden Code geerbt
    • True: Keine Vererbung von Flags, nur explizit angegebene Flags werden verwendet
    Beispiel
    from __future__ import division
    
    # dont_inherit=False - Erbt division-Flag
    code_1 = compile("print(3/2)", filename="<string>", mode="exec", dont_inherit=False)
    exec(code_1)
    
    # dont_inherit=True - Erbt kein Flag
    code_2 = compile("print(3/2)", filename="<string>", mode="exec", dont_inherit=True)
    exec(code_2)
    1.5
    1.5

    Parameter optimize

    Diese Option steuert die Optimierungsstufe des Compilers.

    Werte:

    • -1 (Standard): Verwendet die Optimierungsstufe des Interpreters (__debug__)
    • 0: Keine Optimierung, assert Statements bleiben erhalten, __debug__ ist True
    • 1: Basis-Optimierung, assert Statements werden entfernt, __debug__ ist False
    • 2: Zusätzliche Optimierung, Docstrings werden entfernt
    Beispiel
    code_with_assert = compile("""
    x = -2
    assert x > 0, "x muss positiv sein"
    print(x)
    """, filename="<string>", mode="exec", optimize=0)
    try:
        exec(code_with_assert)
    except AssertionError as e:
        print(f"Fehler: {e}")
    
    code_without_assert = compile("""
    x = -2
    assert x > 0, "x muss positiv sein"
    print(x)
    """, filename="<string>", mode="exec", optimize=1)
    exec(code_without_assert)
    Fehler: x muss positiv sein
    -2

    In diesem Beispiel sehen wir, dass code_with_assert tatsächlich in einen Fehler AssertionError hineinläuft, weil assert im Code verbleibt.

    Bei code_without_assert brauchen wir kein try Block und können direkt den Code ausführen, da assert entfernt wird.

    Rückgabewert

    Die compile() Funktion gibt ein Code-Objekt (code object) vom Typ code zurück.

    Beispiel
    code = compile("x = 10", filename="<string>", mode="exec")
    print(type(code))
    <class 'code'>

    Eigenschaften eines Code-Objekts

    Ein Code-Objekt hat mehrere nützliche Attribute.

    Im ersten Beispiel geben wir den Dateinamen aus.

    Beispiel
    code = compile("x = 10", filename="<string>", mode="exec")
    
    # Dateiname
    print(code.co_filename)
    
    # Name der Funktion/des Moduls
    print(code.co_name)
    
    # Anzahl der Argument
    print(code.co_argcount)
    
    # Variablennamen
    print(code.co_varnames)
    
    # Konstanten im Code
    print(code.co_consts)
    
    # Bytecode (als bytes)
    print(code.co_code)
    <string>
    <module>
    0
    ()
    (10, None)
    b'\x80\x00^\nt\x00R\x01#\x00'

    Verwendung des Code-Objekts

    Das zurückgegebene Code-Objekt kann mit exec() oder eval() ausgeführt werden.

    Beispiel
    code = compile("result = 2 + 2", filename="<string>", mode="exec")
    
    # Ausführen
    exec(code)
    print(result)
    4

    Performance Vorteil

    Der Hauptvorteil von compile() zeigt sich bei wiederholter Ausführung. Schauen wir uns ein Beispiel, bestehend aus zwei Abschnitten an. Einmal mit und einmal ohne compile().

    Beispiel: Ohne compile()
    import time
    
    start = time.time()
    for i in range(1000000):
        exec("x = i * 2")
    
    print(f"Zeit: {time.time() - start:.4f}")
    Zeit: 3.2580

    Nun verwenden wir die compile() Funktion.

    Beispiel: Mit compile()
    import time
    
    code = compile("x = i * 2", filename="<string>", mode="exec")
    start = time.time()
    for i in range(1000000):
        exec(code)
    
    print(f"Zeit: {time.time() - start:.4f}")
    Zeit: 0.1171

    Man sieht den Unterschied deutlich. Im ersten Fall wird der String bei jedem Durchlauf neu geparst und kompiliert. Im zweiten Fall erfolgt die Kompilierung nur einmal, die Ausführung ist dann deutlich schneller.

    Syntax Validierung

    Die Funktion compile() prüft die Syntax vor der Ausführung. Somit können Fehler frühzeitig abgefangen werden, bevor der Code in einer kritischen Situation ausgeführt wird.

    Dabei kann der Code auch aus einer Datei kommen.

    Schauen wir uns diese Beispiele mal an.

    Beispiel - Code direkt
    try:
        code = compile("print('Hallo'", filename="<string>", mode="exec")
    except SyntaxError as e:
        print(f"Syntaxfehler: {e}")
    Syntaxfehler: '(' was never closed (<string>, line 1)

    Nun laden wir den Code aus einer Datei. Dazu erstellen wir zusätzlich die Datei external_code.py.

    external_code.py
    var_one = "Hello"
    var_two = 42
    
    def external_function()
            return var_one

    Diese Datei hat einen Syntaxfehler. Nun schauen wir, wie sich unser Hauptprogramm verhält, wenn wir den Code aus dieser Datei laden und versuchen auszuführen.

    main.py
    with open("./external_code.py", "r") as source_file:
        source_code = source_file.read()
    
    try:
        code = compile(source_code, filename="<string>", mode="exec")
    except SyntaxError as e:
        print(f"Syntaxfehler: {e}")
    Syntaxfehler: expected ':' (<string>, line 4)

    Namespace Isolation

    Das kompilierte Code-Objekt hat keinen eigenen Namespace. Bei der Ausführung mit exec() oder eval() muss der Namespace explizit übergeben werden.

    Beispiel: Namespace
    code = compile("x = 100", filename="<string>", mode="exec")
    
    my_namespace = {}
    exec(code, my_namespace)
    
    print(my_namespace["x"])
    print("x" in globals())
    100
    False

    Wie wir in diesem Beispiel drüber sehen, wird code im eigendefinierten Namespace my_namespace ausgeführt.

    Man kann, falls für die Ausführung notwendig, auch weitere Python-Module und Funktionen selektiv für ein gesondertes Namespace bereitstellen.

    Beispiel: Namespace mit Extras
    safe_namespace = {
        "__builtins__": {
            "print": print,
            "len": len,
            "range": range
        }
    }
    
    user_input = "print('Hallo')"
    
    try:
        code = compile(user_input, filename="<safe>", mode="exec")
        exec(code, safe_namespace)
    except Exception as e:
        print(f"Fehler: {e}")
    Hallo

    Wenn wir nun beispielsweise die Funktion print aus dem __builtins__ Dictionary des eigendefinierten Namespaces entfernen (wir stellen es einfach in diesem Namespace nicht bereit), erhalten wir einen Fehler, da diese Funktion dann nicht mehr bekannt ist.

    Beispiel: Namespace mit Extras
    safe_namespace = {
        "__builtins__": {
            "len": len,
            "range": range
        }
    }
    
    user_input = "print('Hallo')"
    
    try:
        code = compile(user_input, filename="<safe>", mode="exec")
        exec(code, safe_namespace)
    except Exception as e:
        print(f"Fehler: {e}")
    Fehler: name 'print' is not defined