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.
- Kompilierungsphase: Der Quellcode wird in Bytecode kompiliert
- 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
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 Moduleeval- Evaluierungsmodus für einzelne Ausdrückesingle- 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.
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>
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
Nonezurück - Verwendet für: Scripte, Module, mehrere Anweisungen
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
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
Nonezurück, aber druckt das Ergebnis von Ausdrücken automatisch - Wird mit
exec()ausgeführt - Verwendet für : REPL-ähnliche Umgebungen
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:printals Funktion statt Statementunicode_literals.compiler_flag: String-Literale sind Unicodeabsolute_import.compiler_flag: Absolute Imports standardmäßig
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 geerbtTrue: Keine Vererbung von Flags, nur explizit angegebene Flags werden verwendet
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.5Parameter optimize
Diese Option steuert die Optimierungsstufe des Compilers.
Werte:
-1(Standard): Verwendet die Optimierungsstufe des Interpreters (__debug__)0: Keine Optimierung,assertStatements bleiben erhalten,__debug__istTrue1: Basis-Optimierung,assertStatements werden entfernt,__debug__istFalse2: Zusätzliche Optimierung, Docstrings werden entfernt
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
-2In 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.
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.
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.
code = compile("result = 2 + 2", filename="<string>", mode="exec")
# Ausführen
exec(code)
print(result)4Performance 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().
import time
start = time.time()
for i in range(1000000):
exec("x = i * 2")
print(f"Zeit: {time.time() - start:.4f}")Zeit: 3.2580Nun verwenden wir die compile() Funktion.
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.1171Man 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.
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.
var_one = "Hello"
var_two = 42
def external_function()
return var_oneDiese 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.
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.
code = compile("x = 100", filename="<string>", mode="exec")
my_namespace = {}
exec(code, my_namespace)
print(my_namespace["x"])
print("x" in globals())100
FalseWie 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.
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}")HalloWenn 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.
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